DEV Community

spO0q
spO0q

Posted on

PHP Frameworks: hidden errors to avoid

Frameworks such as Symfony (7.2 at the time of writing) or Laravel are highly customizable and encourage good practices, regardless of your experience and skills.

However, you may still introduce design, security, or performance issues.

Symfony: don't call the $container directly

❌ This one is a classic but still heavily used by developers:

 class LuckyController extends AbstractController
  {
      public function index()
       {
        $myDependency = $this->container->get(MyDependencyInterface::class);
        // 
       }
Enter fullscreen mode Exit fullscreen mode

It's possible because the parent AbstractController defines $container as protected:

protected ContainerInterface $container;
Enter fullscreen mode Exit fullscreen mode

Source: Symfony - GitHub

While it does work, it's a bad practice for various reasons:

  • it harms readability
  • it's harder to test
  • it relies on global states ($container)
  • it could lead to incompatibility issues in the future, as Symfony evolves

✅ Use dependency injection in the constructor instead:

 class LuckyController extends AbstractController
  {
      public function __construct(private MyDependencyInterface $myDependency) {}
Enter fullscreen mode Exit fullscreen mode

Eloquent ORM: don't use raw queries blindly

Eloquent allows writing SQL queries quite conveniently.

Instead of writing SQL queries directly, developers can use PHP wrappers to interact with database entities.

It also uses SQL bindings behind the scene, so you get injection protection for free, even with untrusted inputs:

User::where('email', $request->input('email'))->get();
Enter fullscreen mode Exit fullscreen mode

❌ However, when you use helpers like whereRaw, you may introduce vulnerabilities:

User::whereRaw('email = "'. $request->input('email'). '"')->get();
Enter fullscreen mode Exit fullscreen mode

✅ At least, always use SQL bindings:

User::whereRaw('email = :email', ['email' => $request->input('email')])->get();
Enter fullscreen mode Exit fullscreen mode

N.B.: the above example does not make sense, but it keeps things simple. In real-world use cases, you may need whereRaw for optimization purposes or to implement very specific where conditions.

Laravel: what about CSRF?

With CSRF attacks, hackers force the end users to execute unwanted actions on an app in which they're currently authenticated.

Laravel has a built-in mechanism to guard against such scenario.

Roughly speaking, it adds a token (hidden field) to be sent along with your requests, so you can verify that "the authenticated user is the person actually making the requests to the application."

Fair enough.

❌ However, some apps skip this implementation.

✅ Whether you should use the built-in middleware is not relevant here, but ensure your app is secured against CSRF attacks.

You may read this page for more details about the implementation in Laravel.

Please secure AJAX requests too.

Eloquent ORM: queries are not optimized "automagically"

Eloquent allows eager/lazy loading, and supports various optimizations, such as query caching, indexing, or batch processing.

However, it does not prevent all performance issues, especially on large datasets.

❌ It's not uncommon to see such loops:

$users = User::all();
foreach ($users as $user) {
    // some code
}
Enter fullscreen mode Exit fullscreen mode

But it can lead to memory issues.

✅ When possible, a better approach would leverage Laravel collections and helpers like chunk:

User::chunk(200, function (Collection $users) {
    foreach ($users as $user) {
        // ...
    }
});
Enter fullscreen mode Exit fullscreen mode

Check the documentation for more details.

N.B.: It works, but don't forget those helpers are only meant to ease the implementation, so you have to monitor slow queries and other bottlenecks.

Symfony: SRP services

As the documentation says:

useful objects are called services and each service lives inside a very special object called the service container. The container allows you to centralize the way objects are constructed. It makes your life easier, promotes a strong architecture and is super fast!

In other words, you will write custom services to handle specific responsibilities for your application.

❌ The documentation is right. It aims to promote a strong architecture, but it's not uncommon to read services that break the Single Responsibility Principle (SRP):

class OverComplexService
  {
     public function __construct(
        private ProductRepository $productRepository,
        private InvoiceRepository $invoiceRepository,
        private EmailService $emailService,
     );
  }
Enter fullscreen mode Exit fullscreen mode

✅ You must respect that principle. It will be easier to test and maintain.

Symfony: public vs. private services

❌ With Symfony, you can use $container->get('service_id') to call any public service.

We saw earlier that calling the $container like that is considered as a bad practice.

You may not resist the temptation to make all services public, so you can retrieve them by their ID almost everywhere in the project.

Don't do that.

✅ Instead, keep most custom services private and use dependency injection.

Wrap up

Hopefully, you will avoid what I like to call "latent errors" or "hidden errors" with frameworks.

If you don't know how to implement it, trust the framework, but be aware some mechanisms may not be enabled by default.

Top comments (0)