1. Laravel tips & pitfalls

    Laravel seems a popular PHP framework, but all is not beautiful. Here is my growing list of opinionated tips for its users. These observations are mainly based on Laravel version 5.2.

    1. To start with the most important tip: do not trust the documentation that you IDE may show, or infer from the code.

      First of all the source code is not fully documented. Intention, parameters and return values are mostly not described.

      Secondly, the methods exposed by the facades, which are not facades as commonly understood, use runtime magic to invoke the method even if the class does not define it.

      And finally, Laravel seems more in favor of not using the structural possibilities that PHP offers as a object-oriented language.

      • Sometimes classes must implement methods, but no interface is defined to indicate that. Here's an example of this.
      • Some files, like routes.php are included as source code in a class, resulting in unclear scope and context when editing the code in these files.
      • Instead of having separate classes and mechanisms to register custom configuration, magic variables are used to influence behavior. For example, models need to use special variables ($dates, $timestamps, $connection) for the database connection or data representation.
    2. Do not use Validator::resolver(). It can only be used once, so multiple packages that call it will override the current configuration.
    3. Do not use the exists validator because it requires table and columns names instead of taking these details from the Eloquent model. Better create a validator that abstracts these database details away.
    4. To make sure the current versions of files are published from vendor resources always include the --force option:
      php artisan vendor:publish --force
    5. Beware of the $incrementing property on Eloquent models. From the name I expected it to be a switch to inform Eloquent to handle primary key setting or not. Instead this property tells Laravel an auto-incrementing primary key is used by the backing database.

      If you notice a primary key property of a model is NULL after a save() the value of this property may be the cause.

    6. The response() helper does not return the same fluent interface as returned by response()->json(). So the order of calls is important and this won't work:
      $response = response()
          ->header(
              'Location',
              route('GET_ORDER', ['id' => $order->id])
          )->json(
              ['id' => $order->id],
              Response::HTTP_CREATED
          )
      

      it should be:

      $response = response()
          ->json(
              ['id' => $order->id],
              Response::HTTP_CREATED
          )->header(
              'Location',
              route('GET_ORDER', ['id' => $order->id])
          )
      
    7. The Model::toJson() method does not include data from models defined as relationships (even though the documentation says so). To include them, eager loading must have been used at the time the model was retrieved, like this:

      MyModel::with('relation.nested_relation')

      If it's unsure the model to serialize is complete, load related models first:

      $model->load('relation.nested_relation');
      $model->toJson();
      
    8. You can create 2 routes where one has a fixed part and another has a variable in the same position, like this:

      Route::get('/product/types', ...
      Route::get('/product/{id}', ...

      To make this work the order of defining these routes in code is significant. The fixed route must be defined first or it will not work.

    9. The Eloquent ORM does not include the ability to track object changes and persist them in one method call. To add a model with its related models or update all persisted objects changed during processing of a HTTP request you have to write your own storage calls.

      If the backing database is not defined with complete consistency rules I suggest putting all those updates in a single transaction to keep data consistent.

    10. Eloquent does not transform MySQL DATE columns into a date representing object, like DateTime. The proposed solution to cast the column to a datetime or date type does not work (it throws an exception). Including the column in the dates array, a different property related to date transformation, gives the same result.
    11. To disable CSRF on specific routes or for testing purposes repeat the URL of the route in the $except property of the App\Http\Middleware\VerifyCsrfToken class. It's not possible to set it as a route configuration and cannot use the route itself, you must repeat the URL.

      The $except is a duplicated instance property from on the parent class. It's a bit of strange construction to redefine the same property if it already exists. And it's strange to use an instance property, a static seems more logical. This construction looks to be used to prevent having to call a method to register a route as an exception.

    12. There is no mechanism to add or retrieve middleware groups in a package. The possibility of adding individual middleware class is exposed, but not the groups. To add middleware you can use prependMiddleware and pushMiddleware methods on \App\Http\Kernel\Kernel when booting your service provider:

      public function boot(\App\Http\Kernel $kernel) {
          $kernel->pushMiddleware(
              MyMiddleware::class
          );
      
      $kernel->prependMiddleware(
              \package\OtherMiddleware::class
          );
      }
    13. Laravel is a web framework but it lacks some features I think should be natively included. For these topics you must create your own solution or find one:

      • CORS handling
      • configure routes based on HTTP headers, like Content-Type or Accept.
    14. When a provided Node script that can be used for Vue support, like watch-poll, fails with an error related to a missing cross-env script the solution may be to manually install this required package for development:

      npm install --save-dev cross-env
    15. The asset() helper includes the protocol in the generated link. This makes it harder to switch between http and https environments. It would be better if no protocol was included and // used to prevent problems.

      Now if your application only has http assets it's not a problem. And if all assets are served over https, use the secure_asset() helper. If a mix is needed, look for a different solution.

      Oh, and if you access the function through the URL facade, the name is different, it's called secureHttp().

    16. Similar to the previous item, the route() helper does not detect the protocol of the current request if its https. Adding URL::forceSchema('https'); in the boot method of the AppServiceProvider fixes it. (found here).

    17. The Storage facade and the storage_path() function use different base paths. Storage works based on the storage/app folder, storage_path() uses storage. When using both methods to access files with take care of the differences in calculated paths.
    18. When defining a database table the $table->timestamps() method adds the columns created_at and update_at to track row changes. The problem is that these columns are defined with NULL allowed. This means changes made to the data outside the model will not track keep track of the information automatically. Better declare the these columns in the table definition to make sure they are always set.
    19. The task schedular starts tasks when running a database migration. I did not expect this because a migration is not a normal application invocation. In my case creating the initial database schema failed. It failed because the time to run a scheduled task could not be retrieved from the database. That is the same database that the migration should create. It's not considered a bug, so you have to find a different solution.

      To be fair, the schedular is not a really a scheduling system. It's a low level mechanism to run a function using a time-based condition. It does not track execution or produce a system wide report of all results. You can't create a sequence of jobs or continue a job when interrupted.

    20. Laravel includes an exception handler that shows the famous Whoops page. It's difficult to replace because the exception handler not only processes exceptions. Instead program flow of some other components, like the validation system and user authentication, rely on the exception handler redirecting the client.

    21. When registering a Closure as middleware in the constructor of a controller, that code is not always called. If the route is defined with a closure that creates the constructor and calls a method, middleware is not executed. When defining the route with a string structured as "ClassName@method" the middleware closure is called.

    22. The authorization system does not monitor access to application functions, check are inserted in the protected code itself. Ideally an authorization system protects access to functions no matter how the action is invoked. In Laravel the URL can be protected, or a function can ask if the current user may do something or not. But that's up to the developers to remember to add these checks. And since they are part of the PHP code it's not possible to rename or split roles later.