Blog

  • DO Repeat Yourself (Laravel Edition)

    This story was originally published on beqode.com with proper code syntax highlighting and formatting. Read it there, give it a clap here.

    OK, the title is clickbait, go against the “Don’t Repeat Yourself” (DRY) principle and you’ll trigger the Internet.

    Obviously we don’t want to repeat ourselves a.k.a copy/paste, a.k.a spaghetti code ™, a.k.a “Don’t touch this piece”, a.k.a high maintenance cost code…

    But there are (few) areas where it is even recommended to repeat yourself and I wanted to highlight them only because I made the mistakes myself when I was a junior developer.

    Controllers

    Here I refer to a controller as the method inside a class controller, the direct “controller” of a view, resource, etc…

    Controllers can repeat themselves.

    Well, that is if and only if you’ve been separating your domains properly:

    • Repositories to query your DB/Model
    • Services to extract your business logic

    Once everything is separated, it is quite normal for 2 controllers to look alike:

    class UserController
    {
        protected $users;
    
        public function __construct(Users $users)
        {
            $this->users = $users;
        }
    
        public function show(Request $request, string $username)
        {
            $user = $this->users->findByUsername($username);
    
            if (!$user) {
                return abort(404);
            }
    
            return view('user.show', compact('user'));
        }
    
        public function edit(Request $request, string $username)
        {
            $user = $this->users->findByUsername($username);
    
            if (!$user) {
                return abort(404);
            }
            return view('user.edit', compact('user'));
        }
    }

    It’s obviously OK to repeat:

    $user = $this->users->findByUsername($username);
    
    if (!$user) {
        return abort(404);
    }

    Whereas if you did not have the Users repository, that would not be acceptable:

    class UserController
    {
        public function show(Request $request, string $username)
    
        {
            $user = User::with('posts')
                ->whereNotNull('email_verified_at')
                ->whereNull('banned_at')
                ->where('username', $username)
                ->first();
    
            // ...
        }
    
        public function edit(Request $request, string $username)
        {
            $user = User::with('posts')
                ->whereNotNull('email_verified_at')
                ->whereNull('banned_at')
                ->where('username', $username)
                ->first();
    
            // ...
        }
    }

    To make my point even clearer: you should NOT attempt to group show and edit together in order to stay DRY:

    class UserController
    {
        // ...
    
        public function show(Request $request, string $username)
        {
            return $this->showOrEdit($request, $username, 'show');
        }
    
        public function edit(Request $request, string $username)
        {
            return $this->showOrEdit($request, $username, 'edit');
        }
    
        protected function showOrEdit(Request $request, string $username, string $action)
        {
            $user = $this->users->findByUsername($username);
    
            if (!$user) {
                return abort(404);
            }
    
            return view('user.' . $action, compact('user'));
        }
    }

    While this could seem like a good idea now (it is not), it is very likely the 2 controllers will evolve their own separate ways and you’ll end up with a showOrEdit full of if and very prone to errors ! It’s basically added complexity for very little gains and at the cost of readability.

    Even worse, trying to group edit and show together inside the controller instead of properly extracting the business logic into the Users repository !

    class UserController
    {
        // ...
    
        protected function showOrEdit(Request $request, string $username, string $action)
        {
            $user = User::with('posts')
                ->whereNotNull('email_verified_at')
                ->whereNull('banned_at')
                ->where('username', $username)
                ->first();
            // ...
        }
    }

    So do repeat your controllers, but only when they are composed with your different repositories & services.

    You could even OOP’ing your class controllers (inheritance) in order to group functionalities and stay DRY, this is almost certainly a bad idea ! class controllers are merely connectors to your views and should not hold any business logic (which is what the inheritance pattern is likely trying to achieve).

    All of the above comes down to the “Fat model, skinny controllers” mantra. You can have as many skinny controllers as you want, and because they are skinny, because all your business logic (repositories, services) has been extracted, they will be very similar and that’s completely fine ! Do repeat those skinny controllers!

    Views

    Another area where you shouldn’t sweat it too long for keeping your code DRY is Views. One with good intentions could spend way too much time trying to re-use views, splitting them into multiple partials, make use of layout inheritance, etc…

    Views are very very likely to change over time (copy, CSS…) and trying to optimise reusability is in my experience/opinion, not worth the effort.

    Views are very unique by definition, they fulfil a very specific (display) purpose. While it seems like 2 similar views could be grouped together in a partial, it’s rarely true, misleading and will almost certainly end up in unmaintainable code.

    See Views as disposable pieces of code: Copy them, delete them, replace them without too much thinking.

    A concrete example is trying to make one unique component for two different list items.

    Say you have a _booking-item.twig partial, it displays a Booking item in a list of upcoming bookings in your Admin area:

    <article class="booking">
        <h2 class="booking__title">New Booking #{{ booking.id }}</h2>
        <div class="booking__buttons">
            <button>Accept</button>
            <button>Reject</button>
        </div>
    </article>

    Now you have a new requirement to display a list of archived bookings in a tab next to your upcoming bookings list.

    It is tempting to reuse _booking-item.twig and to write some conditional logic in it:

    <article class="booking{% if booking.isArchived() %} booking--archived{% endif %}">
        {% if booking.isUpcoming() %}
            <h2 class="booking__title">New Booking #{{ booking.id }}</h2>
        {% else %}
            <h2 class="booking__title">Booking #{{ booking.id }}</h2>
        {% endif %}
        {% if booking.isArchived() %}
            <p>Processed on {{ booking.processed_at|date('M d, Y') }}</p>
        {% endif %}
        {% if booking->isUpcoming() %}
            <div class="booking__buttons">
                <button>Accept</button>
                <button>Reject</button>
            </div>
        {% endif %}
    </article>

    While clean-ish, this is not (at all) scalable.

    Also remember that the above is not a real world example, and I would even argue that this simple view is already bloated with needless logic that prevents me from understanding the final result of the view.

    When I open a view I want to instantly have a feeling of what it will look like. Views are unimportant pieces of code, they are swappable, deletable, replaceable…

    If we rewrite our _booking-item.twig partial into two different partials:

    {# resources/views/_booking-item.twig (Unchanged) #}
    <article class="booking">
        <h2 class="booking__title">New Booking #{{ booking.id }}</h2>
        <div class="booking__buttons">
            <button>Accept</button>
            <button>Reject</button>
        </div>
    </article>
    {# resources/views/_booking-archive-item.twig #}
    <article class="booking booking--archived">
        <h2 class="booking__title">Booking #{{ booking.id }}</h2>
        <p>Processed on {{ booking.processed_at|date('M d, Y') }}</p>
    </article>

    I have a much better understanding of what the final result will look like.

    Yes, if you change the booking__title class name in your CSS to booking__heading, you do have to edit 2+ files to change the class name in your views. That’s the trade-off for readability.

    There is no hard line of when to group or split your views, but in my experience don’t be afraid to repeat yourself in your views, this is very much unimportant.

    The point I am trying to make is: Views may not be DRY. I would even say they should not be DRY. This will save you a lot of time, again, Views are disposable, write them, leave them, come back to them, edit them, delete them, they should not be in the way, they should not delay the greater Architecture you have to design in your business logic, they must take very little of your precious bandwidth.

    Conclusion

    There is a limit to DRY, and that limit is: Once you’ve been separating your different concerns properly, you can Repeat Yourself (RY) ™.

    Especially (and only ?) in areas like Controllers & Views.

    Source: https://medium.com/beqode/do-repeat-yourself-laravel-edition-c4a058f587f6

  • A Look at the New PHP 8.1 Fibers Feature

    PHP is trying to shove the lack of features from its code base, and Fibers is one of the meaningful additions to the language. PHP Fibers, incoming in PHP 8.1 at the end of the year, will introduce a sort of async programming (coroutines) into the wild.

    The fiber concept basically refers to a lightweight thread of execution (also called coroutine). These seem to run in parallel but are ultimately handled by the runtime itself rather than by pushing it directly to the CPU. A lot of major languages have their own ways to implement them, but the principle is the same: Let the computer do two or more things at the same time, and wait until everything is completed.

    PHP implementation of Fibers is not true asynchronous computing, as one may think. Indeed, the core of PHP will be still synchronous after Fibers are introduced.

    You can think of PHP Fibers as being like switching from one car to another.

    How Will Fibers Work?

    A Fiber is a single final class that looks like a car: It can start and run immediately, push the brakes and wait, and resume its trip.

    final class Fiber
    {
        public function __construct(callable $callback) {}
        public function start(mixed ...$args): mixed {}
        public function resume(mixed $value = null): mixed {}
        public function throw(Throwable $exception): mixed {}
        public function isStarted(): bool {}
        public function isSuspended(): bool {}
        public function isRunning(): bool {}
        public function isTerminated(): bool {}
        public function getReturn(): mixed {}
        public static function this(): ?self {}
        public static function suspend(mixed $value = null): mixed {}
    }

    When you create a new Fiber instance with a callable, nothing will happen. Is not until you start the Fiber that the callback is executed like any other normal PHP code.

    $fiber = new Fiber(function() : void {
        echo "I'm running a Fiber, yay!";
    });
    
    $fiber->start(); // I'm running a Fiber, yay!

    Didn’t I say Fibers were asynchronous? They are, but only until you hit the brakes by calling Fiber::suspend() inside the callback. Then it passes the control to the “outside,” but bear in mind this Fiber car is still alive and waiting to resume.

    $fiber = new Fiber(function() : void {
        Fiber::suspend();
        echo "I'm running a Fiber, yay!";
    });
    
    $fiber->;start(); // [Nothing happens]

    Now that the car is suspended, the next thing to do is to take your foot off the brake, and for that, we can call the resume() method from the outside.

    $fiber = new Fiber(function() : void {
       Fiber::suspend();
       echo "I'm running a Fiber, yay!";
    });
    $fiber->start(); // [Nothing happened]
    $fiber->resume(); // I'm running a Fiber, yay!

    This is literally untrue async, but that doesn’t mean your application can’t do two things at a time. The real truth here is that the Fiber function state is saved where it was left off. You’re figuratively switching between cars, driving each to one point.

    One of the neat things about start(), suspend(), and resume() is that they accept arguments:

    • The start() method will pass the arguments to the callable and will return whatever the suspend() method receives.
    • The suspend() method returns whatever value the resume() method received.
    • The resume() method returns whatever the next call to suspend() received.

    This makes communication between the Main thread and the Fiber relatively easy:

    • resume() is used to put values into the Fiber that are received with suspend(), and
    • suspend() is used to push values out that are received by resume().

    This makes the official example way easier to understand:

    $fiber = new Fiber(function (): void {
        $value = Fiber::suspend('fiber');
        echo "Value used to resume fiber: ", $value, "\n";
    });
    
    $value = $fiber->start();
    
    echo "Value from fiber suspending: ", $value, "\n";
    
    $fiber->resume('test');

    If you execute the above code, you will receive something like this:

    Value from fiber suspending: fiber
    Value used to resume fiber: test

    We’re Close To Having Our Own Complete Web Server

    Let’s face it, PHP is paired with nginx/Apache 99% of the time, mainly because it is not multithreaded. The server that comes in PHP is blocking and serves only for some tests or showing something to a client.

    Fibers may open the door to letting PHP work with the socket more efficiently, and enable things like WebSockets, server-side events, pooled database connections, or even HTTP/3, without having to resort to compiling extensions, hacking your way down with unintended features, encapsulating PHP into another external runtime, or any other recipe for disaster.

    Some things may take time to settle, but if there is a promise of keeping a single code base for other features, without having to spend days trying to compile and deploy, I’m on board.

    You Won’t Use Fibers Directly

    According to the documentation, Fibers offers “only the bare minimum required to allow user code to implement full-stack coroutines or green-threads in PHP.”

    In other words, unless you have a very weird reason to use them directly, you will never have to interact with Fibers like you would doing coroutines on Javascript or Go.

    Some high-level frameworks (like Symfony, Laravel, CodeIgniter, and CakePHP, among others) will take some time to understand how to approach Fibers and create a set of tools for them to work with from a developer standpoint. Some low-level frameworks, like amphp and ReactPHP, have already boarded the Fiber ship in their latest development versions.

    cartoon about how standards proliferate
    Standards — xkcd

    While this will free you from thinking more about Fibers rather than your idea, it means that everyone will make their own flavor of concurrency, with all their advantages and caveats.

    Only One Fiber at a Time

    two runners competing in a racetwo runners competing in a race
    Photo by Victoire Joncheray on Unsplash

    I’m going to quote Aaron Piotrowski from PHP Internals Podcast #74:

    “Since only one Fiber can be executing at the same time, you don’t have some of the same race conditions that you have with memory being accessed or written to by two threads at the same time.”

    Aaron also adds that it will be the frameworks that will be able to tackle the concurrency and synchronization problem over the same piece of memory.

    This is good because you won’t need to think about data races, semaphores, and mutexes, things that gophers understand perfectly. But you’re still bound to essentially only two things at a time, no matter what you do.

    No Channels in the Meantime

    Since only one Fiber is running at the same time, even if you declare multiple ones, there is no problem of synchronization of data. But Aaron said that there is the potential of another Fiber waking up and rewriting what the first Fiber is sharing. One of the solutions is to use Go’s channels style.

    Derick Rethans asked about channels, and Aaron’s answer is simple: Something else must be implemented alongside Fibers, but until then, the frameworks will have the last word on how to use channels, if they deem them necessary for what they offer, like the guys at amphp.

    Comparing With the New Kid on the Block

    The Go language has been gaining traction a lot of these months, especially thanks to being built around concurrency from the ground up. Anything can be executed concurrently with the go keyword, and synchronization is done by mutexes or channels, making it brain-dead easy to work with.

    names := make(chan string)
    go doFoo(names)
    go doBar(names)

    From that perspective, Go is miles ahead of PHP’s initial concurrency solution. If you need something fully multithreaded, you may want to make your software in Go, or even Rust if you want to use CPU threads directly.

    It’s not that PHP is not compatible with any concurrency model, but surely its foundation is still synchronous at its core, at the expense of convenience and better understandability. Compared to Go, the latter suffers from excessive plumbing.

    If you wanted a true concurrency model like Go, then PHP would have to be rewritten from the very ground up, but that would open a lot of possibilities in a computing world that has already embraced multithreading.

    Hopefully, this will bring more focus on features and independence, something that I have been very vocal about.

    Source: https://betterprogramming.pub/a-look-at-the-new-php-8-1-fibers-feature-979489399918