I'm an experienced web dev (10+ years mostly Ruby/JS) but new to PHP and Laravel. I inherited a project written in Laravel and would like to get up to speed on best practices. What would you recommend for me?
Answers to your questions
I'm an experienced web dev (10+ years mostly Ruby/JS) but new to PHP and Laravel. I inherited a project written in Laravel and would like to get up to speed on best practices. What would you recommend for me?
[https://github.com/alexeymezenin/laravel-best-practices](https://github.com/alexeymezenin/laravel-best-practices)
First thing I would do is read the documentation for Laravel like a book, “cover to cover”. It’s very easy to read, and doing so will give you a sense of what the framework can and can’t do. It will also give you a rough idea of where to go back to in order to re-read about a certain topic.
Next I would read this to get up to speed on modern PHP best practices:
https://phptherightway.com/
For a couple specific Laravel practices I follow:
# 1. Use dependency injection
Lots of Laravel examples use the global “facades” for brevity, and while Laravel ships with utilities to make mocking and testing with those facades possible, it’s more conventional in PHP and programming in general to use standard dependency injection to inject dependencies instead. Laravel will automatically resolve dependencies from the typehints if it can. [Read more about Laravel’s service container](https://laravel.com/docs/6.x/container) to know how to bind concrete implementations to interfaces so it knows how to resolve them. A related concept is service providers where you register those bindings: https://laravel.com/docs/6.x/providers
# 2. Be disciplined with Eloquent usage
Eloquent is an active record ORM. The way it’s implemented is that a given Eloquent model can have numerous distinct behaviors:
### 1. As an instance of a single row in the database
$userInstance = User::find(1);
$name = $userInstance->name;
### 2. As as table gateway for fetching many rows
$userInstance1 = User::find(34);
// this is also valid and very confusing:
$users = $userInstance1->where(‘active’, 1)->get();
// Same as
$users = User::where(‘active’, 1)->get();
// Which is the same as
__construct(User $user) {
$users = $user->where(‘active’, 1)->get();
}
// Which is the same as
$user = new User;
$users = $user->where(‘active’, 1)->get();
That’s right: you can re-use a given Eloquent *instance* of a row in the database as a gateway for finding other instances. You can use it statically, or you can use an injected “instance” of it, or just new up an instance of it.
None of that flexibility is a good thing IMO, and it can lead to extremely confusing code where you’re using instances to find other instances, or you don’t know if an instance is representing a row in the database or not.
This is an inherent problem with active record in general, but Eloquent’s smearing of responsibilities is particularly bad.
So to overcome that problem, I follow a few rules:
### 1. If I am injecting an Eloquent model in a constructor, it means I plan on using it as a table gateway, and I name it as such:
__construct(User $userGateway) {
$users = $userGateway->where(‘active’, 1)->get();
}
### 2. I never, ever use a row instance for anything more than a model of a table row. These are no-nos:
$user = User::find(53);
// Do not use these when working with a row instance:
$user->where(…)
$user->find(…);
$user->create(…);
$user->whereHas(…);
$user->firstOrNew(…);
$user->firstOrCreate(…);
### 3. I never mix and match instance methods with gateway methods. I treat Eloquent models as row instances ONLY!
class User extends Model {
// Row instance method
public function getFavoriteGames() {
…
}
// Gateway method
public function activeUsers() {
return $this->where(‘active’, 1)->get();
}
// Static gateway method
public static function activeUsers() {
return static::where(‘active’, 1)->get();
}
}
Trust me on this, don’t mix them (especially not static and instance methods). If you want to put re-usable gateway methods somewhere, do it in a repository class or literally any other service class dedicated to gateway-like behavior.
class UserRepository {
private $userGateway;
constructor(User $userGateway) {
$this->userGateway = $userGateway;
}
public function findActiveUsers() {
return $this->userGateway->where(‘active’, 1)->get();
}
}
For more advanced, disciplined usage in larger applications, I recommend this 3-part article series, which is what has shaped a lot of my perception of Eloquent usage:
* https://medium.com/laravel-news/separation-of-concerns-with-laravel-s-eloquent-part-1-an-introduction-c9a6dc6b4a65
* https://medium.com/@jon.lemaitre/separation-of-concerns-with-laravel-s-eloquent-part-2-putting-it-into-practice-cb9426c8c11b
* https://medium.com/@jon.lemaitre/separation-of-concerns-with-laravel-s-eloquent-part-3-collections-relations-eager-loading-and-e13530a8890a
I don’t go quite to that level of detail on most projects, but I have found the recommendation to define interfaces for your row instances and return those interfaces from your repositories to be immensely useful in really keeping your row instances well defined.
#3. Don’t use magic attributes
Here is the documentation for them:
https://laravel.com/docs/6.x/eloquent-mutators#accessors-and-mutators
I don’t use them. They are needless cognitive overhead.
When I see `$user->full_name` somewhere, I’m going to assume that this is a column in the database. But instead, it might be defined as a magic accessor: `getFullNameAttribute()`. This is a needless mental translation you have to do: `full_name` = `getFullNameAttribute()`. Why add that kind of cognitive load to your code base?
If you want to get the user’s full name, just use normal PHP:
public function getFullName() {
return $this->first_name . ‘ ‘ . $this->last_name;
}
$user->getFullName();
See? Simple, straight-forward, no mental mapping to do, and your IDE can make it easy to quick jump directly to it, and it’s more performant. If you follow the guide I linked to, you can even define an interface that expresses that method (and methods that wrap other attributes)
I wrote a book about this question: https://stitcher.io/laravel-beyond-crud
Couple of quick thoughts for you, less specific, more basic than a few other comments.
1. Follow examples in the official docs pointedly, which will insulate your codebase against most breaking changes in future releases.
2. Use first party packages when possible, e.g. Passport, as they are maintained by the community.
3. Consider Laravel’s default structure for organizing your code by folder or shift the codebase to a DDD approach and replicate all items like controllers, notifications, events, jobs, listeners, etc. into each domain.
4. Learn to use middleware properly, highly effective in logging events and early authentication checks.
5. Use gates and policies, separately or combined.
6. Keep your controllers lean. When they bloat, consider shifting validation logic to request classes, move logical behavior to the model layer or a repository, authorization logic to a policy or gate.
7. Use traits for shared model logic or behavior.
8. Use the migration pattern as defined in the docs as opposed to running SQL directly.
9. Laravel is often used as an API consumed by a React, Angular, or Vue front-end. There is no preference to your front-end choice.
10. Heavy use of blade and view templates is not as maintainable as a JS component-based front-end methodology.
11. Follow REST practices in your controllers, more controllers are fine. Use single-action controllers (\_\_invoke()) whenever needed.
12. Follow your own or a consistent set of principles like the JSON API guide in managing your request responses. I don’t follow the JSON API to a T as it’s too layered for me, but I hide my DB structure via response objects. I don’t expose my IDs for example, prefer UUID.
13. Watch for the n+1 problem (see docs for helper tools) as Eloquent will not stop you from making far too many SQL calls, e.g. pay attention to both lazy and eager-loading.
14. Learn the artisan flags that are available as they can accelerate your development, e.g. artisan make:controller ProductController –api –model=Product.
15. Lots of good articles on people who move away from Eloquent or use a repository pattern to make such a future move less painful.
16. Concern over heavy use of your own facades, e.g. (custom facade) MyAppName::someAction(). Whereas, encourage use of methods on a model facade.
17. See YT for past broadcasts of Laracon, much available in terms of video content. Sign up for the digital Laracon Online 2020 happening on 2/26, only $25.
18. See the forums at Jeffrey Way’s site, [Laracasts.com](https://Laracasts.com), for robust discussion on most things Laravel and PHP. Jeff’s video content targets a more junior audience than you generally, but he’s got more than a 1000 clips which can serve as a great repository when you need an opinion on a more advanced content. His video content is really the go to source for many. Very open, nice community, good for asking questions.
19. See the forums at [forum.vuejs.org](https://forum.vuejs.org) for a lot of Laravel crossover by individuals who are developing Vue apps with a Laravel API.
20. Adam Wathan for Laravel,Vue, Tailwind and Wes Bos for Javascript are both very helpful, see their reasonably priced courses (not cheap but helpful).
21. Udemy has a lot of Laravel content, hundreds of courses. Some content is pretty decent, hard to separate out the best. If you can spend $100-200, wait for a Udemy sale at $9-12/course and pick up a few such that you can keyword search or observe their strategies.
22. There are often a few decent Laravel streamers on Twitch, not every day, but often, good place to talk with varied skill, opinionated devs.
23. Packt and others have published a number of books over past years on Laravel best practices. I would absolutely spend the $ on books like Laravel: Up & Running by Matt Stauffer or Full-Stack Vue,js 2 and Laravel 5 by Anthony Gore (he’s got 5 or so quality L. courses on Udemy). May be worthwhile picking up a few ebooks, note no need herein if you read the docs formally.
24. Bottom line by my read would be: Prefer a component front-end over Blade (i don’t use blade or view templates), follow REST with no variations if possible, define all web or api routes with careful attention to route resources when possible and middleware definitions in route groups. Then, use dependency injection as others have noted in controllers, keep controller logic very defined making use of companion classes for various behavior or logic (e.g. Validation, Notifications, Logging), use Response classes to carefully control what is sent back by the API, prefer UUID, hide database structure via response objects, use first party packages, use 3rd party packages sparingly and those that are well maintained, and finally don’t worry too much about bloated models (better than bloated controllers). Docs docs docs.
25. A final note, make use of Laravel’s myriad unit and feature test capabilities, mocks, etc. While I don’t practice TDD formally as it’s too cumbersome for my taste, I write tests for every single thing that my apps should and should not do. Several Laravel authors have great content on TDD in Laravel.
Hope that helps!
Learn and use Eloquent. Many newcomers try to shoehorn SQL into their projects. Eloquent is safer and very nice once you learn it.
Which version is the project? A legacy 4.2 project is worlds different from 6.0