Category: Refactoring

  • Refactoring Advanced Eloquent Filters To Collections

    Amo Chohan wrote a very good article detailing a technique he uses to clean up controllers when dealing with complex search queries in eloquent.

    I’m also enjoying Adam Wathan‘s latest release: Refactoring to Collections – a read guaranteed to make you smarter.

    Amo really nailed the functionality here – but I think the final class presented could use some of Adam’s magic to make it just a little bit better. Let’s be honest – I’ve just been looking for an excuse to try out some of Adam’s techniques…

    Below is the original class, and then my refactors.

    <?php
    
    namespace App\UserSearch;
    
    use App\User;
    use Illuminate\Http\Request;
    use Illuminate\Database\Eloquent\Builder;
    
    class UserSearch
    {
        public static function apply(Request $filters)
        {
            $query = 
                static::applyDecoratorsFromRequest(
                    $filters, (new User)->newQuery()
                );
    
            return static::getResults($query);
        }
        
        private static function applyDecoratorsFromRequest(Request $request, Builder $query)
        {
            foreach ($request->all() as $filterName => $value) {
    
                $decorator = static::createFilterDecorator($filterName);
    
                if (static::isValidDecorator($decorator)) {
                    $query = $decorator::apply($query, $value);
                }
    
            }
            return $query;
        }
        
        private static function createFilterDecorator($name)
        {
            return return __NAMESPACE__ . '\\Filters\\' . 
                str_replace(' ', '', 
                    ucwords(str_replace('_', ' ', $name)));
        }
        
        private static function isValidDecorator($decorator)
        {
            return class_exists($decorator);
        }
    
        private static function getResults(Builder $query)
        {
            return $query->get();
        }
    }

    Removing Static

    Since all methods in this class are static, we can’t keep track of state. We end up passing around the variables that we’re using (the Request and Builder in this instance) to every method.

    By adding a private constructor, we can keep the Api of the class the same, but internally gain the benefits of working with an object!

    protected $filters;
    protected $query;
    
    private function __construct(Request $request, Builder $query)
    {
        $this->filters = collect($request->all());
        $this->query = $query;
    }
    
    public static function apply(Request $request)
    {
        return (new self($request, User::query())->applyFilters()->getResults();
    }

    I’ve refactored the apply method to create a new instance of the object, and then call the appropriate methods to return the desired results. Since we don’t need the actual Request object anywhere else, I’ve gone ahead and created a collection out of the inputs for future use.

    Introducing a Null Object

    Currently we’re looping through the request inputs, and checking to see if we can find an existing filter to apply:

    foreach ($request->all() as $filterName => $value) {
    
        $decorator = static::createFilterDecorator($filterName);
    
        if (static::isValidDecorator($decorator)) {
            $query = $decorator::apply($query, $value);
        }
    }

    One way to get rid of that conditional is by introducing a null object that effectively accepts the query, but does nothing with it.

    private function getFilterFor($name)
    {
        $filterClassName = $this->createFilterDecorator($name);
    
        if (! class_exists($filterClassName)) {
            return new NullFilter;
        }
    
        return new $filterClassName;
    }

    The above method allows us to refactor the applyDecoratorsFromRequest method (that I’ve renamed to applyFilters) into the following:

    private function applyFilters()
    {
        $this->filters->each(function ($value, $filterName) {
            $this->getFilterFor($filterName)->apply($this->query, $value);
        });
    
        return $this;
    }

    Since our filters are now a collection, we have access to the each method which I’ve used here instead. The getFilterFor method will always return a valid class that we can use – even if it is just a null object – allowing us to forego any conditionals!

    I’m returning $this just for convenience’s sake to allow me to chain my method calls.

    Alternatively…

    A refactor to my own method getFiltersFor could also more heavily make use of collections, and we can end up with a solution that completely eliminates loops and conditionals:

    private function getFilterFor($name)
    {
        return $this->filters->map(function ($value, $filterName) {
            return $this->createFilterDecorator($filterName);
        })->filter(function ($filterClassName) {
            return class_exists($filterClassName);
        })->map(function ($filterClassName) {
            return new $filterClassName;
        })->get($name, new NullFilter); 
    }

    We’ve reworked the flow to do the following:

    • Map all filter names to class names
    • Filter out class names that aren’t implemented
    • Map all class names into new instances
    • Get the appropriate filter object, or return a new NullFilter if it doesn’t exist

    In this case it’s debatable as to whether this second refactor adds any value – but an interesting solution nonetheless.

    Moving On

    For those who suffer from long and complicated index methods on your controllers, I urge you to read the original article by Amo.

    As a side note, all of the refactors that I performed on the class (creating a private instance, introducing a null object, and refactoring to collections) were all lessons learned from Adam Wathan’s Refactoring to Collections book.

    Happy coding!

  • Things I Used To Do (That Aren’t Cool Now)

    I’m the proud owner of WhichBeach – the website that tells you which beach is best to visit (in Malta) based on the weather, updated hourly.

    As I’m picking up the codebase ahead of this Summer’s updates, I can’t help but review and refactor the existing code. It’s interesting to dissect previous design decisions. Also, I’ve learnt so much in the last year that it’s only natural for me to want to bring the standard of an older project up a bit.

    Here’s a list of things that I used to do, that aren’t cool now.

    I used to not have a coding style guide

    I was blissfully unaware that coding style guides even existed! Sure – I had my own patterns that I tried to stick to, but they weren’t enforced.

    The solution to this one is easy – I’ve already converted the codebase to PSR-2 – though it was somewhat of a manual process of opening each file and running the PHP Coding Standards Fixer. I’m sure there’s a quicker way through the terminal that I’m not aware of – perhaps someone can enlighten me in the comments.

    Edit:

    Julio pointed out in the comments that the command to convert your files to PSR-2 is the following:

    php-cs-fixer fix --level=psr2 path-to-your-files/

    Thanks Julio!

    I used to group my files by layer

    I organised my code into top level folders like:

    • Handlers
    • Helpers
    • Interfaces
    • Listeners
    • Observers
    • Queues

    I’m sure it felt like I was being organised while I was doing it, but it turned out to be highly impractical: you can’t glance at a folder (say “Beaches”) and get an overview of all that business logic that is associated to Beaches – the logic is scattered across different parts of the system.

    Also, looking inside these Layer folders, you find code that is quite unrelated to each other, and unlikely to be used together.

    I’ve recently been experimenting with Vue and Vueify – the approach taken there is that since the Javascript and Html work so closely together… put them in the same file!

    A more general rule might be: Code that works together should be kept together.

    The solution to this problem is to carefully refactor and reorganise my code structure.

    I used to code without automated tests

    I’m scared to change any of the core logic of this project! Sometimes I have to pull in live data to see how the system reacts! I can’t test drive anything!

    These are all fears that I used to live without, but haunt me today. A slight exaggeration – yes, but legitimate concerns when moving forward with the project.

    The solution is in the works. I’ve set up a testing environment with an in memory database, and have started adding tests to existing functionality. I’m refactoring along the way using the Test Driven approach.

    My predicament? Should I first add as many tests as possible to the existing codebase, and then refactor? Or should I refactor as I write the tests, so that the first time the test is written, it changes the code to be inline with my new rules of development? I’m leaning towards the second option, but I have my doubts.

    I used to do a lot of image manipulation myself

    This stuff is hard. Yes, I used available packages that handled the image manipulations, but I still had to handle a lot of the logic myself.

    At the moment, the payoff isn’t worth the work and maintenance. My solution is to get rid of most of my Image related logic, and instead use Spatie’s wonderful Media Library Package. I use it for a number of other projects and it fits the bill perfectly.

    I used to have self validating models

    A couple of years ago, this seemed to be the thing to do – but I’ve been bitten by this approach a couple of times.

    Using Laravel’s Form Request Validation is a good way to get some code out of my Models (the validation rules) and Controllers (the validation logic).

    I used to have an administration area

    This isn’t a “bad decision” per se – but it doesn’t align with my plans for the future – which is to be a user powered app where logged in users are enabled to do things like add new beaches, comments and reviews.

    The solution? Rip out all the administration area! (Yikes!) Then, slowly test drive the front-end driven way to get data into the website from logged in users. I’ll still need to have an authorization system where some content is only editable by administrators – but the idea is to have a lot of user driven content.

    Let me know in the comments if you’ve had experience with this before!


    The above is a rough game plan for the future of WhichBeach, and a reminder that as developers we’re always evolving!

    Time to get to work.