Larger Laravel Projects: 5 Things to Take Care Of
Probably the hardest step in a development career is jumping from a simple CRUD. That like project in the early years to something more advanced with a larger architecture and higher-level responsibility for code quality. So, in this article, I try to list the questions (and some answers) to consider when dealing with large (r) Laravel projects.
This post will be filled with external links to my own content and community resources, so feel free to check it out.
Disclaimer: What is a large project?
First, I want to explain what I mean by “big”. Some people measure the number of database records, such as a million rows in a user table, which is huge. Yes, but it’s a large database, not the Laravel project itself.
By large projects I mean mainly the number of entities to manage. Simply put, how many Eloquent models your project has. If you have many models it usually means complexity. This way, as a secondary measure number, you can count the number of routes or common controller methods.
With such a large scope of work, there are often multiple developers working on the project, which brings the complexity of managing the codebase.
Furthermore, a third non-technical characteristic of large projects is the price of error. I want to highlight projects where your inefficient or broken code can cost real money: say a 30 minute downtime on an online store could easily cost the business $10,000. Or, a few broken if statements could cause a real tens of people not to place an order.
So yeah, I’m going to talk about those big projects below.
1. Automated testing
In smaller projects, there is usually a smaller budget and a bigger push to start “something” faster, so automated testing is often seen as a “bonus feature” and ignored.
In larger projects, you can’t manually test all features before release. You can test your own code, yes, but you don’t know how it will affect older code written by others. Heck, you might not even know how other code or modules work because you’re focusing on various parts of your application.
So how do you make sure the code you post doesn’t cause bugs? A lot of times new code is just a refactoring of old code, so if you change something in your project structure, how would you test that it didn’t break? Don’t get caught up in what I call “Fingers Crossed Driven Development” mentality.
Also, back to the definition of a larger project – remember, mistakes are costly. So, literally, your broken code could cost the business financially. If this argument still doesn’t convince you to cover your code with tests, then probably nothing else.
Yes, I know the typical argument of “we don’t have time to write tests”. I have a full video.
But this is where you need to find that time. It involves some communication: evaluating deadlines, thinking about when to write tests, and also discussing with your manager what will happen if you don’t write tests. Then they will understand and allow extra time. If they don’t, it means they don’t care much about quality, then maybe it’s time to look for another company?
Now, I’m not necessarily talking about the mythical “100% test coverage”. If you’re really under time pressure, choose features that are critical to the functioning of your application for testing. As Matt Stauffer famously said, “First, write tests for features, and if the tests fail, you lose your job”. So anything related to payments, user access, stability of core most used functions.
2. Architecture and Project Structure
Ah yes, a million dollar question: how to structure a Laravel project? Back in 2019, I even posted a 5-hour course on the subject, but I still feel like I’ve only scratched the surface there.
You can follow many different models or ideas: divide your project into modules, use a DDD approach, pick some from design patterns, or just follow SOLID principles. It’s all personal preference.
The problem is that there is no magic bullet and a one-size-fits-all approach. For example, no one can claim that all larger Laravel projects should follow DDD. In some cases, even SOLID principles are sometimes considered suboptimal.
But the problem is clear: as your project structure grows, you need to change something and restructure the files/folders/classes into something more manageable. So what are the basic things you should do?
First, move the content to a subfolder and name everything accordingly. Again, the example from Monica CRM is very good.
Then, make sure your class/method is not too big. There’s no magic number to follow, but if you feel like you need to scroll up and down a lot, or spend too much time figuring out what a class/method does, it’s time to refactor and move parts of your code elsewhere. The most common example is when the controller file is too large.
These are just two suggestions, but it’s these two changes that make your code more readable, maintainable, and testable.
Yes, sometimes it requires a big “risky” refactoring of classes, but hey, you probably have automated tests to check everything, right? correct?
3. “Fake data” on plants and seeds
A topic related to automated testing that we have already discussed. If you want to stress test application functionality, you need a lot of data. Plant + Seed is the perfect combination to easily achieve this.
Just get in the habit of creating plants and seeds right from the start when creating a new Eloquent model. Well, whoever will use it to generate some fake data in the future will be very grateful.
But it’s not just about testing. Also, consider a fresh install of your application. Successful large projects tend to only get bigger, so you definitely have to recruit new developers. If they didn’t have any sample data to work with, how difficult would they have had to install the process and speed it up?
You may also need to install the application multiple times on various servers – local, staging, some Docker-based environments, etc. You can customize the seed to run in production or locally.
4. Database structure
While I mentioned at the beginning that database size is not the definition of a large Laravel project, database structure is a very important thing for long-term performance and maintainability.
Which relationships are used? In Laravel, should it be HasOne? There are many? belong to many? Polymorphism?
Also, other questions. One large table or several small tables? ENUM field or relation? UUID or ID column? Of course each case is individual and I have a full course on structured databases, but this is my main short tip.
Try to ask your “future self” what potential SQL queries will be on these database tables and try to write those queries first.
In other words, think about the end goal and reverse engineer the structure accordingly. It will help you “feel” the correct structure.
If you have the plant and seed ready (notice the pattern of how the topics in this article help each other?), you’ll be able to easily simulate future use and even weigh options A vs. B and decide which is the right one. This moment is actually very important: changing the database structure in the future, with lots of real-time data, is probably one of the most complex/expensive/risky changes. So you better make a good decision in advance.
That said, if you do need to refactor your database, you shouldn’t be afraid to refactor. Move some data into a separate less-used table, change HasMany to Polymorphic, choose other column types, etc.
Just make sure you don’t lose any customer data.
5. External packages and Laravel upgrades
When you choose a Laravel/PHP package to include in your composer.json, it’s easy at first: just use the latest version of everything, and make sure the package is useful.
But later, when the project exists for a year or two, it needs to upgrade the version. Not only Laravel itself, but also packages.
Luckily, Laravel moved from a 6-month release to an annual release (later synced the Laravel 9 release with Symfony), so developers no longer have the headache of every 6 months.
In general, the framework itself has a fairly stable kernel, and upgrading to a newer version is relatively easy and should only take a few hours. Also, a service called Laravel Shift is a huge help for developers looking to save time.
But the problem is with the package you are using.
Very typical scenario: You want to upgrade your project to a new Laravel version, but some packages in your composer file haven’t released their new version to support Laravel upgrade. So in my experience, project upgrades happen at least a few months after Laravel is officially released, at which point package creators catch up.
And, there are even worse situations: when package creators don’t have time to release upgrades (remember, most of them are free, in their spare time), or even abandon packages. so what should I do now?
First, of course, you can help the creator and submit a Pull Request with suggested upgrades (don’t forget to include automated tests). But even then they need to review, test and approve your PR, so I rarely see this happening in real life. These packages are either actively maintained or close to obsolete. So the only reasonable solution is to fork the package and use your own version in the future.
However, a better decision is to think more deeply when choosing which package to use. The questions to ask are: “Do we really need that package?” and “Does the package creator have a reputation for maintaining their package?”