Laravel: The Flip Side of Popularity, or What They Don't Tell You at Conferences

Laravel: The Flip Side of Popularity, or What They Don't Tell You at Conferences

Laravel is, without a doubt, one of the cornerstones of modern PHP development. Its popularity is off the charts, and for good reason: a low barrier to entry, a huge community, a rich ecosystem, and the ability to quickly build projects ranging from simple to moderately complex. The integration with Inertia.js is a game-changer, allowing for relatively painless SPA development while enjoying all the benefits of a full-stack approach. However, like any technology, Laravel has its "skeletons in the closet" – aspects that can cause quite a few headaches, especially in large, long-running projects. Let's delve into them.

1. The Eternal Battle for Types

Although PHP is actively moving towards strict typing, and Laravel tries to keep pace, type-related issues remain relevant. Yes, with each new version, the framework becomes more typed, but it's still far from perfect. In projects where high reliability and code predictability are crucial, such as CRM systems where errors can cost real money, insufficient type strictness periodically leads to unexpected runtime bugs that could have been caught during development.

2. Inertia.js: Careful, Everything's Exposed!

Inertia.js is indeed a breakthrough for Laravel in the SPA world. But there's a nuance often overlooked by newcomers (and even experienced developers sometimes fall into this trap): all data you pass from the backend to frontend components becomes visible on the client side. A classic example from e-commerce development: accidentally passing an array of all active promo codes to a product card component. Seems trivial, but it's an unpleasant surprise. Hence the golden rule: always use Data Transfer Objects (DTOs) and meticulously filter what goes to the frontend. This is especially critical in CRMs, where you might inadvertently expose internal deal statuses or personal manager data not intended for client eyes.

3. Livewire: PHP Magic Instead of JavaScript – Not for Everyone

Livewire is marketed as a way to write interactive interfaces without leaving the cozy world of PHP. For simple things, like dynamic list filtering or auto-updating a block, it can be convenient. But personally, Livewire and I just didn't click. The very idea of writing in PHP what should inherently be JavaScript strikes me as somewhat unnatural. Instead of leveraging the full power and flexibility of modern JS (and its ecosystem), you have to learn new syntax and put up with limitations. My attempt to implement a large nested form with numerous dynamic dependencies and cascading filters using Livewire turned into a fiasco. Ultimately, if I have to choose between Inertia.js and Livewire, my vote always goes to the former.

4. Sole Governance: The Peculiarities of Decision-Making in Laravel

Compared to Symfony, which has a strong community and collective decision-making, Laravel operates somewhat differently. Key decisions regarding the framework's development, Pull Request acceptance, and new feature implementation essentially rest in the hands of one person – Taylor Otwell. The number of core contributors is strictly limited, and they are mostly Laravel employees. This approach gives rise to some rather peculiar processes. It's not uncommon for critically important issues, actively discussed by the community, to be ultimately resolved by Taylor as he sees fit, even if it contradicts the arguments of the majority. A prime example is the issue with `truncate`, which could cascade and affect other tables in PostgreSQL (GitHub issue #35157). For a long time, Taylor refused to fix this bug, citing backward compatibility, even though the problem only affected one database driver, and there was no question of any global backward compatibility issue.

This governance model understandably frustrates some users, and frankly, this is rarely seen in other major open-source projects. Taylor himself mentioned in an interview that he might reject a PR and then implement the same functionality himself, simply to better understand how it works. Such an approach, in my opinion, somewhat undermines the spirit of Open Source and reduces developers' motivation to contribute to the project.

5. Migrations: Years of Pain

Imagine a project that has been actively developed for five, seven, or even ten years. New tables and fields are constantly added, and existing ones are modified. How many migrations will accumulate over this time? Hundreds, if not thousands. Now, imagine how long it takes to set up such a project from scratch, with each of these migrations being applied sequentially. For a long time, Laravel's core lacked a tool for "squashing" (combining) migrations, a problem most other frameworks solved about fifteen years ago, for instance, through a single schema file reflecting the current DB state.

Taylor long resisted incorporating such a mechanism into the core, deeming it unnecessary. Only relatively recently has it become possible to generate an SQL schema file that is used during migrations instead of running all old files. But, firstly, it's not the default behavior. Secondly, it doesn't work as smoothly as in other frameworks: after creating a migration, the schema isn't updated automatically, requiring additional steps. And yet, many libraries and development tools rely on an up-to-date schema file for autocompletion and code analysis. In CRMs, where the data structure can be quite extensive and change frequently, managing such a large number of migrations becomes a real ordeal.

6. Middleware: Layers of Confusion

How do you define all middleware in Laravel? By default, this is done in the `bootstrap/app.php` file (in versions before Laravel 11, it was `app/Http/Kernel.php`). For a small application, no problem. But as soon as the project grows, this file turns into a hard-to-read list. How do you apply middleware to a specific route or group of routes? This can be done in `routes/web.php` or `routes/api.php` files. However, understanding the overall middleware application structure, figuring out which layer is used where, becomes very challenging. The `bootstrap/app.php` (or `Kernel.php`) file provides a clearer overview of groups but doesn't allow assigning middleware to individual routes with the same flexibility.

The new syntax in Laravel 11+ for `bootstrap/app.php`:

->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('group-name', [
        First::class,
        Second::class,
    ]);
    $middleware->prependToGroup('group-name', [
        Third::class,
        Fourth::class,
    ]);
})

 

This does offer some improvement for groups, but the general problem of readability and layer management in large applications persists. To be fair, in Symfony, which doesn't really have middleware in the traditional sense but uses an event and listener system instead, the request processing logic can be even more opaque and convoluted.

7. Caching: "Just clear the cache!"

This is, perhaps, one of the biggest pain points in Laravel. Back when I first started with the framework, I found the go-to answer for many problems—"Clear the cache"—incredibly frustrating. You're working on something, can't figure out why it's broken, you search online, and there it is: "php artisan cache:clear", "php artisan config:clear", "php artisan route:clear", "php artisan view:clear"... Laravel has several types of cache, and it's not always clear which one does what or which specific one needs to be cleared at any given moment.

Yes, there are technical reasons for this, and if you’re inclined, you can dig into the nuances. But why should I have to spend time learning the quirks of the framework's own caching, especially in a local environment? I remember days I'd bang my head against a problem for hours, only for it to be solved by a single cache-clearing command. And I'm not talking about the application cache (data we cache ourselves), but the framework's internal cache—for configuration, routes, and views. The core issue is that Laravel caches environment variable values (from the .env file) when `config:cache` is run. This approach is not only inconvenient during development but also flies in the face of the Twelve-Factor App principles, which call for strict separation between config and code.

8. No Proper Out-of-the-Box Message Bus

Laravel has a "Jobs" system that allows for performing deferred operations. This is handy for sending emails, processing images, etc. But if you need to orchestrate communication between multiple microservices, especially if not all of them are Laravel-based, Jobs aren't really the best fit. It's hard to call Laravel's Jobs system a full-fledged message bus in the traditional sense. Yes, the framework lets you dispatch an event to RabbitMQ, for example, but you can't flexibly control the message format or define your own queue handling logic without significant custom work. For complex inter-service communication scenarios, you either have to integrate third-party solutions or write a lot of custom code.

9. JSON Translations: No Subfolder Support

Laravel offers two ways to store translation files: as PHP arrays or in JSON files. If you opt for PHP files, you can neatly organize them into subfolders, like lang/en/user.php, lang/en/product.php. This is very convenient for large multilingual projects. However, if for some reason you decide to use JSON files for translations (perhaps for easier frontend parsing), then be prepared for all keys for a single language to reside in one file (lang/en.json). There's no option to break them down into logical subfolders, as you can with PHP files. This can lead to massive, unwieldy JSON files.

10. IDE Autocompletion: Magic at the Cost of Convenience

The debate about whether using facades and "magic" in frameworks is good or bad rages on. My take: magic is great when it's predictable and doesn't get in the way of development. In Laravel, however, "magic" often means you can't get proper autocompletion in your IDE without additional tools. For your IDE to understand that `User::create()` has such a method, you need to install a special plugin (like Laravel Idea for PhpStorm), because the static `create()` method doesn't actually exist in the `User` class; it's "magically" proxied through a facade. If you want to go without plugins, you're stuck writing more verbose code like `User::query()->create()`. It's a similar story with model field autocompletion – IDEs won't see them without plugins.

11. hasManyThrough: The Argument Enigma

I've specifically dedicated a point to this Eloquent relationship definition method. No matter how long I've used Laravel, I've never managed to commit the order of arguments in `hasManyThrough` to memory. Every single time I need to use it, I have to look it up in the docs. Why they couldn't have come up with a more intuitive syntax, perhaps something like `hasMany(Deployment::class)->through(IntermediateTable::class)->hasMany(Environment::class)` (a simplified example to get the point across), remains a mystery to me.

12. Traits, Traits Everywhere

Laravel uses traits very actively. They are literally everywhere. On one hand, this allows for code reuse and adding functionality to classes. But on the flip side, it makes the source code much harder to read and understand. It's not uncommon to find traits using other traits, which in turn use yet others, and so on. As a result, we encounter the so-called "Yo-yo problem," where understanding the logic of a single method requires bouncing around a chain of several traits, trying to keep this whole tangled web of inheritance and mixins straight in your head.

13. Models: Too "Forgiving" by Default

In Eloquent models, useful options like `strictMode` and `preventAccessingMissingAttributes` (or `preventSilentlyDiscardingAttributes` in newer versions) are turned off by default. They need to be explicitly enabled in a service provider or a base model. Unfortunately, on many projects that land in our laps for support, these features aren't enabled. And what's the result? Some really weird behavior where you mistakenly access a non-existent model property or try to assign a value to a non-existent attribute, and Laravel just "swallows" it without any errors or warnings (depending on the specific option and version). This can hide bugs and lead to problems that are a nightmare to debug, especially if you accidentally mistype a field name during a mass attribute assignment in a CRM, where data accuracy is paramount.

Conclusion

Despite these drawbacks, Laravel has been and continues to be a powerful tool for web development. Its strengths often outweigh its weaknesses, especially for certain types of projects. However, it's crucial to be aware of the "dark side" as well, to be prepared for potential difficulties and to make informed decisions when choosing a technology stack or during long-term support of a Laravel project. Understanding these nuances will help you write better quality code and steer clear of many common pitfalls.

 

Popular Posts

My most popular posts

Maximum productivity on remote job
Business

Maximum productivity on remote job

I started my own business and intentionally did my best to work from anywhere in the world. Sometimes I sit with my office with a large 27-inch monitor in my apartment in Cheboksary. Sometimes I’m in the office or in some cafe in another city.

Hello! I am Sergey Emelyanov and I am hardworker
Business PHP

Hello! I am Sergey Emelyanov and I am hardworker

I am a programmer. I am an entrepreneur in my heart. I started making money from the age of 11, in the harsh 90s, handing over glassware to a local store and exchanging it for sweets. I earned so much that was enough for various snacks.

Hire Professional CRM developer for $25 per hour

I will make time for your project. Knowledge of Vtiger CRM, SuiteCRM, Laravel, Vue.js, Wordpress. I offer cooperation options that will help you take advantage of external experience, optimize costs and reduce risks. Full transparency of all stages of work and accounting for time costs. Pay only development working hours after accepting the task. Accept PayPal and Payoneer payment systems. How to hire professional developer? Just fill in the form

Telegram
@sergeyem
Telephone
+4915211100235
Email