Source Next: https://javascript.plainenglish.io/react-native-push-notifications-guide-for-rookies-and-techies-c890db40761f
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
andedit
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 ofif
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
andshow
together inside the controller instead of properly extracting the business logic into theUsers
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 tobooking__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()
, andresume()
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 withsuspend()
, andsuspend()
is used to push values out that are received byresume()
.
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.
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
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
- The
-
The Downsides of Coding With Laravel Octane
Laravel Octane was announced some weeks ago with the promise of pushing the framework to the moon in terms of requests served per second. It wasn’t public until today (in beta form) for everyone to test, with first-party Laravel package compatibility (e.g. Nova or Telescope) out of the box.
The way Laravel Octane works to push the boundaries is essentially simple: Instead of “starting” the application each request, it does it fully once and saves it into a shared part of the memory. Once done, Octane will spawn multiple PHP workers that will receive this shared application instance. These processes handle not one but multiple requests at the same time.
As many of you know, PHP is a single-thread process at its core and will still be even after the arrival of PHP Fibers. Laravel Octane does not use PHP Fibers, but alternatively, it runs the application under a custom HTTP server with tighter control over PHP. For the latter, Roadrunner or Swoole is required.
The amazing performance does come with some code caveats.
One Instance To Rule Them All
If you figured out the problem, you’re awesome. But for those who are still lost, this a very simplified version of how it works:
When the server starts, the application starts up by registering and booting all the services — even those marked as “deferred,” which we call “bootstrap.” This fresh instance is then shared across all PHP Workers. This is contrary to how a normal PHP process works. Typically, the application bootstraps and is destroyed each time a request goes in and a response goes out.
I’m going to quote this excellent and extensive Diving Laravel article about how Laravel Octane works — specifically how the application and the service singletons are handed out — which will clear your mind immediately:
“Bindings in the container can be registered as singletons, these special bindings are only going to be resolved once in an application lifetime. The resolved instances will be stored in the container cache and the same instances will be re-used during the lifetime of the application.
(…) Instances are resolved by calling $app->resolve(‘singleton’) or $app->make(‘singleton’). So, if you resolve any singletons inside your service providers’ boot or register methods, these singletons will persist. Singletons that are resolved while handling requests won’t persist tho, they will get constructed on every request when running under Octane. You don’t need to worry about those.”
Since the application has been already bootstrapped, the app works at blast speed since there is no need to bootstrap again. Now, imagine the shenanigans: A service singleton will keep the same data for all the requests from anybody! And this also applies to the config repository and the container itself. Bollocks!
If you want to break Laravel Octane, you can even do a simple memory leak by piling up data into a
Class
static property for each request.MyLeaker::$pileUp = Str::random(100);
Coding With Laravel Octane in Mind
Now that you know how it works, it’s very easy to understand that the application, as a whole, is essentially the same in all aspects across all requests. Changing the state globally in the bootstrapping will rebound on all requests, and outside of it, it will only work for the current one. The application doesn’t die once the request is out, but it will immediately serve the next one that comes into the server.
With that in mind, here are my recommendations if you plan to create an application with Laravel Octane or create a package that promises the same:
- It’s safe to register singletons if you expect them to be resolved only once and to stay the same across requests.
- It’s safe to inject the config if you expect immutability from it.
- It’s safe to inject the container to resolve a service registered at bootstrap, but not to add or modify something inside it and expect it to persist.
- Always try to use
request()
outside controllers. - Never use static properties. Use constants instead when possible.
In short, the application state at bootstrapping is different from the application instance at request time. Also, singletons should not be directly tied to requests.
Conclusion
While Laravel Octane is not disruptive in any way, as a developer, you will have to double-check your dependencies for any Laravel Octane incompatibility — especially those who deal with many singletons.
I think it is convenient to tell the developers that your code has been revised in order to work with Laravel Octane in a non-disruptive way. I’m even badging my repositories as “Laravel Octane Compatible” once revised so people can jump in faster and more safely.
For the rest, Laravel Octane is not a requisite, as many app bottlenecks are bound to unoptimized database queries, slow session stores, or slow external API requests. As always, benchmark your app to check where the slowness comes from, as Laravel Octane may only give you more headaches than solutions.
Source: https://betterprogramming.pub/the-downsides-of-coding-with-laravel-octane-17f4a7a4ea85