Laravel features you may not know about

Laravel features you may not know about Thumbnail image

Laravel features you may not know about Table of contents

  1. Introduction
  2. Laravel tips and tricks that you might not know
    1. Use the artisan down command to put the Laravel app down, but allow access to certain IP(s)
      1. --message="something here"
      2. --retry
    2. Need to do a query builder 'where' query on a couple of columns? Try this clean, one-method way:
    3. Eloquent's find() can accept multiple rows
    4. Did you know that relations such as belongsTo can provide a default model if the relationship returns null?
    5. Use the render() method on Exceptions to render a HTTP response to the user
    6. The cache's rememberForever($key, function() { return 'something'; }) function
    7. You can pass a file path to your route's group() to include that file
    8. Eloquent: get a fresh version (fresh()) of the current model and duplicate rows (replicate())
    9. Some Routing static methods that are underused...
      1. The main and commonly used routing methods...
      2. Redirecting URLs from within your routes file (web.php)
      3. Does your controller method just return a view? Skip the controller, return the view from the routes file...
      4. Route name prefixes
      5. Fallback Routes
      6. Want to find more?
    10. Use wildcard (*) checks for the current route
    11. Auth::id() instead of Auth::user()->id
    12. Simple Pagination
    13. Laravel's tap() method
    14. Most of Artisan's make: commands will create files in subdirectories you tell it to (and will correctly namespace it)
    15. Quickly log things with logger() and info()
    16. Set model properties such as $perPage
    17. Eloquent Relationships: Only return models if they actually have a relationship row with has()
    18. optional()
    19. Do you use View Composers?
    20. Use array_wrap() to ensure that you are working with an array
    21. Eloquent: Add data to the output from toArray() (or toJson())
    22. Pluralise strings with str_plural()
  3. Ok, that's it!

    Introduction

    Here are some features from Laravel that you may not be aware of. They're not exactly anything advanced or hidden, but I don't really notice that these functions get used or referenced very often. (Maybe they are though, and I've just not noticed it.)

    I decided to post this as a couple of people replied to a comment on Reddit that I made saying that they were not aware of the php artisan down option to whitelist allowed IP addresses/ranges. I'll try and keep this list updated as I come up with new ideas for this. Last updated: 22 Nov 2018.

    Laravel tips and tricks that you might not know

    Use the artisan down command to put the Laravel app down, but allow access to certain IP(s)

    artisan down --allow=123.123.123.123

    The php artisan down command is useful to use when you need to put your Laravel site down for maintenance or when upgrading the system. However, without using the --allow option you have no real way to test it as a regular user. However, you can provide a whitelist of IP addresses to allow.

    You can also provide multiple IP addresses, or even IP ranges:

    php artisan down --allow=127.0.0.1 --allow=192.168.0.0/16

    Some other useful options for the down command include:

    Screenshot of the message: you can write anything here. It shows on the Laravel down page
    --message="something here"

    You can provide a custom message to show users, by using the --message="..." option. This will show that message to your users while the site is down. You can modify it further by copying the 503.blade.php to your views dir. The default message is 'Sorry, we are doing some maintenance. Please check back soon.'.

    php artisan down --message="You can write anything here"

    --retry

    You can set the retry value, which will be used for the Retry-After HTTP header's value.

    php artisan down --retry=60

    Need to do a query builder 'where' query on a couple of columns? Try this clean, one-method way:

    Let's say you want to do the following SQL query:

    1.     SELECT * FROM `users`
    2.     WHERE
    3.         `name` = 'some_name'
    4.         AND `email` = 'some_email'
    5.     LIMIT 1

    You can achieve this with one 'where' method call. Eloquent will work out that the "and" in the middle means two seperate where clauses:

    1. \App\User::whereNameAndEmail('some_name','some_email')->first();
    2.  
    3. // the above has the exact same result as:
    4. \App\User::where("name", 'some_name')->where("email", 'some_email')->first();
    5. // also same as:
    6. \App\User::where(["name" => 'some_name', "email", 'some_email'])->first();

    (change Name and Email as required - the important part is the And between them. Laravel will automatically work out what columns your where statement refers to)

    And as well as "and", you can also do an "or" like this:

    1.  \App\User::whereFooOrBar('foo value','bar value')->first();

    Which will produce the following SQL query:

    1.     SELECT * FROM `users`
    2.     WHERE
    3.         `foo` = 'foo value'
    4.         OR `bar` = 'bar value'
    5.     LIMIT 1
    Eloquent's find() can accept multiple rows

    You can use the query builder's find() method in the following way:

    1. $rows = SomeEloquentModel::find([1,4,6,11]);

    If find() is passed an array, it will return a collection of Eloquent model rows with those IDs (of course, if those rows exist...).

    Also applies to findOrFail(). They both (eventually) just call findMany() if passed an array of IDs to find.

    Did you know that relations such as belongsTo can provide a default model if the relationship returns null?

    I'm sure many others are aware of this, but I only found out about this very recently.

    If you have a relation (a belongsTo, MorphOne or HasOne type), it might sometimes return null. In that case, you can provide a default with the withDefault() method.

    It will automatically work out what type of object to return, but you can also set some default attributes.

    Here are a few examples:

    1. public function user()
    2. {
    3.     return $this->belongsTo('App\User')->withDefault();
    4. }
    5.  
    6.     //or
    7.  
    8. public function user()
    9. {
    10.     return $this->belongsTo('App\User')->withDefault([
    11.         'name' => 'Guest Author',
    12.     ]);
    13. }
    14.  
    15.     // or
    16.  
    17. public function user()
    18. {
    19.     return $this->belongsTo('App\User')->withDefault(function ($user) {
    20.         $user->name = 'Guest Author';
    21.     });
    22. }

    For the implenetation details check out SupportsDefaultModels.php.

    Use the render() method on Exceptions to render a HTTP response to the user

    If you run the following command:

    php artisan make:exception --render SomeExceptionName

    It will create a new exception class in your exceptions directory, with a blank render($request, Exception $exception) method.

    If an exception is thrown and handled by your app/Exceptions/Handler.php file, it will check for method_exists($exception, 'render'). If it exists, it will call that method on the exception and return it to the user.

    Here is an example of a render() method on an exception, which will return a view response:

    1. /**
    2. * Render the exception into an HTTP response.
    3. *
    4. * @param    \Illuminate\Http\Request
    5. * @return  \Illuminate\Http\Response
    6. */
    7. public function render($request)
    8. {
    9.     response()->view('errors.custom.myexception', [], 500);
    10. }

    You can use this method to return a response (such as a view) that will be displayed to the user.

    Before I knew about this, I would hard code some logic in app/Exceptions/Handler.php when I needed to show a certain response to the user for a particular exception. It is much neater, in my opinion, to hard code it in the exception itself than to have a messy Handler.php file.

    The cache's rememberForever($key, function() { return 'something'; }) function

    If you use the cache feature, you almost certainly are doing something like this:

    1. $value = Cache::remember('users', $minutes, function () {
    2. return DB::table('users')->get();
    3. });

    But did you know you can also do this:

    1. $value = Cache::rememberForever('users', function () {
    2. return DB::table('users')->get();
    3. });

    This will get an item from the cache, or store the default value forever.

    You can pass a file path to your route's group() to include that file

    If your routes file is a bit messy, you can stick them in their own file and include the file with a call to group().

    1. Route::middleware(SomeMiddleware::class)->group(__DIR__"/SomeGroup.php")

    As far as I can tell this isn't mentioned in the docs (or I'm being blind at 1 AM and can't find it). Check out the group($callback) method on RouteRegistrar.

    (You could just do a normal require() call as well...)

    Eloquent: get a fresh version (fresh()) of the current model and duplicate rows (replicate())

    Use ->fresh() to query the database and get a fresh version of the current item.

    1. $user = \App\User::first();
    2. $user->name = "Something new";
    3. $user = $user->fresh(); // note that it returns the fresh value, it doesn't affect the current model
    4. dump($user->name); // the original name, not 'something new'

    If you want to rehydrate the existing model, then use refresh():

    1.         $flight = App\Flight::where('number', 'FR 900')->first();
    2.         $flight->number = 'FR 456';
    3.         $flight->refresh();
    4.         $flight->number; // "FR 900"

    If you need to duplicate a Eloquent object, use:

    1. $new = SomeModel::first()->replicate();
    2. $new->save()

    The replicate method lets you provide an array of attributes it should ignore when duplicating ($except=[]), for example:

    1. $new = User::first()->replicate(['password']); // replicate everything APART FROM the password attribute
    2. $new->save(); // $new will have all of the attributes as the first User row, but the 'password' attribute won't be there.
    Some Routing static methods that are underused...
    The main and commonly used routing methods...

    There are 6 'main' routing methods (which almost every web app uses):

    1. Route::get($uri, $callback);
    2. Route::post($uri, $callback);
    3. Route::put($uri, $callback);
    4. Route::patch($uri, $callback);
    5. Route::delete($uri, $callback);
    6. Route::options($uri, $callback); // rarely used (apart from CORS?), IMO

    Plus the following (quite commonly used in my experience):

    • Route::match(['get', 'post'], '/', $callback); if you want the same URI to be available for multiple HTTP methods,
    • Route::any('foo', $callback); if you want any and all HTTP methods to apply for a route

    All of the above, plus a few others such as Route::group(...) are pretty common. But there are a whole bunch of other very useful routing methods that I tend to not really notice very often. They include the following:

    Redirecting URLs from within your routes file (web.php)

    I've seen some pretty big Laravel projects that had half a dozen routes pointing to their own methods on a RedirectionController controller, that simply returned a redirection response. But you can redirect direct in the routes file:

    1. // if someone visits /here, they will get a HTTP redirect to /there
    2. Route::redirect('/here', '/there', 301); // 301 Moved Permanently

    Note: the third param is optional, and it defaults to 302 (temporary) redirect.

    Does your controller method just return a view? Skip the controller, return the view from the routes file...

    If your controller's method is really simple and just returns a view, then you might be able to use the following:

    1. Route::view('/welcome', 'welcome'); // same as returning view("welcome") in a controller
    2. Route::view('/welcome', 'pages.welcome', ['site' => 'WebDevEtc']); // same as returning view("pages.welcome")->withName("WebDevEtc") in a controller
    Route name prefixes

    If you have a bunch of routes, you probably name each of them with things like:

    1.  Route::get("/","[email protected]")->name("blog.show");

    You can avoid having to add the "blog." prefix to each name by using route name prefixes.

    1.     Route::name('blog.')->group(function () {
    2.         Route::get('show', function () {
    3.                     // Route assigned name 'blog.show'...
    4.             })->name('show');
    5.     });

    I have to be honest, I'm not actually keen on this, as it makes searching for a route name a tiny bit more complicated, but it can clean up a busy routes file.

    Fallback Routes

    You can use Route::fallback(...), that will be used if no other routes matched. This is normally used to show a 404 error but can be useful in other situations as well.


    Want to find more?

    There are lots of methods not mentioned here when it comes to routing. Check out the documentation on laravel.com.

    Use wildcard (*) checks for the current route

    If you have some kind of navigation and want to add a class='active', you might be tempted to do something like if( request()->is('control-panel') || request()->is('control-panel/change-email') || request()->is('control-panel/edit-profile') { ... } to check the current URI (or passing an array to is()).

    However, you can just use a wildcard:

    1.     if (request()->is("control-panel*")) { ... }

    You can use routeIs() in a similar way, but with the route name. (You can use this with the above tip (route name prefixes) to ensure that it always matches correctly.)

    Auth::id() instead of Auth::user()->id

    I see Auth::user()->id all of the time (and I had to admit that I do it myself quite a bit), but it is much quicker (to type) Auth::id() to get the ID of the user (or null if not logged in).

    As pointed out on Reddit, rather than doing \Auth::user(), you can just do auth() and use the auth helper function. (Full example: auth()->id())

    Simple Pagination

    The normal pagination will count how many total rows there are and calculate the maximum number of pages. On large datasets, this isn't a good idea. The simple pagination option will only display a previous and next link and does a far quicker query on your database (as it doesn't need a full count of the number of rows).

    You use it in the same way as the normal Laravel pagination, but just call simplePaginate() instead of paginate().

    1. $users = DB::table('users')->simplePaginate(15);

    Did you also know that it can output JSON with full pagination data?

    1.     {
    2.         "total": 50,
    3.         "per_page": 15,
    4.         "current_page": 1,
    5.         "last_page": 4,
    6.         "first_page_url": "http://laravel.app?page=1",
    7.         "last_page_url": "http://laravel.app?page=4",
    8.         "next_page_url": "http://laravel.app?page=2",
    9.         "prev_page_url": null,
    10.         "path": "http://laravel.app",
    11.         "from": 1,
    12.         "to": 15,
    13.         "data":[
    14.             {
    15.                 // Result Object
    16.             },
    17.             {
    18.                 // Result Object
    19.             }
    20.         ]
    21.     }

    I recommend reading everything on the docs if you use pagination (which most of us do), as there are quite a few options when it comes to using the built-in pagination.

    Laravel's tap() method

    Although I've noticed this helper function being used quite a bit, I think it is underused. The tap($val, $callable) function is normally used in a way that lets you run the $callable function with the value of $val (i.e. it will run $callable($val)). Then, whatever the returned value of $callable, it will return $value.

    Here is the source of the function, that will probably explain it better!

    1.         /**
    2.         * Call the given Closure with the given value then return the value.
    3.         *
    4.         * @param  mixed $value
    5.         * @param  callable|null $callback
    6.         * @return  mixed
    7.         */
    8.         function tap($value, $callback = null)
    9.         {
    10.             if (is_null($callback)) {
    11.                 return new HigherOrderTapProxy($value);
    12.             }
    13.  
    14.             $callback($value);
    15.  
    16.             return $value;
    17.         }

    An example of where it is used is in the query builder in Laravel:

    1.     public function create(array $attributes = [])
    2.     {
    3.         return tap($this->newModelInstance($attributes), function ($instance) {
    4.             $instance->save();
    5.         });
    6.     }

    Of course, you could just do something like:

    1.     public function create(array $attributes = [])
    2.     {
    3.         $instance = $this->newModelInstance($attributes);
    4.         $instance->save()
    5.         return $instance;
    6.     }

    However, with the tap() function you can write cleaner code, turning multiple lines into nice one-liners.

    Laravel News have a nice write up (better than the official docs so I recommend giving it a read.

    Most of Artisan's make: commands will create files in subdirectories you tell it to (and will correctly namespace it)

    The following will create the directory (if it does not already exist) /app/Models/, and place an empty model PHP file in it called YourModel.php, and it will be correctly namespaced (\App\Models):

    php artisan make:model -m Models/YourModel

    The command above will also create a migration file for you (thanks to the -m option).

    Now the file will exist at app/Models/YourModel.php, and the migration file in your migrations directory.

    Quickly log things with logger() and info()

    Use info("log some info message") and logger("log a debug message"). It is slightly cleaner than app('log')->debug($message) or \Log::debug($message)...

    Set model properties such as $perPage

    Your Eloquent models almost certainly have properties such as: $fillable = ['title', 'description'], $casts = ['some_field'=>'array'] set.

    BTW, if you don't know about $casts = ['some_property' => 'array', 'size_info'=>'object'] then check it out here. There are many options: integer, real, float, double, decimal (you can use it like: decimal:2 to have max of 2 digits), string, boolean, object, array, collection, date, datetime, and timestamp

    But I rarely notice other properties set on models, including:

    $perPage: Set the (default) number of rows per page, when you use Eloquent's pagination. Of course, you can override this in the pagination call.

    1.     /**
    2.      * The number of models to return for pagination.
    3.      *
    4.      * @var  int
    5.      */
    6.     protected $perPage = 15;

    $incrementing: If you have a table without auto-incrementing ID rows then you will want to set the following to false:

    1.     /**
    2.      * Indicates if the IDs are auto-incrementing.
    3.      *
    4.      * @var  bool
    5.      */
    6.     public $incrementing = true;

    $snakeAttributes: If a model's relationships have snake-casing enabled, Eloquent will snake case the keys so that the relation attribute is snake cased in the returned array to the developers.

    1.     /**
    2.      * Indicates whether attributes are snake cased on arrays.
    3.      *
    4.      * @var  bool
    5.      */
    6.     public static $snakeAttributes = true;
    Eloquent Relationships: Only return models if they actually have a relationship row with has()

    If you have two models, let's say BlogPost and Comment. They are linked by a OneToMany relationship. If you want to get all blog posts along with the comments you could do this:

    1.  $postsWithComments = BlogPost::with("comments")->get();

    However, this will return blog posts that have 0 comments. If you only want to get posts that have > 0 comments, you can do this (has()):

    1.  $postsWithComments = BlogPost::with("comments")->has("comments")->get();
    optional()

    I have to admit that optional() is used very often, but I thought I'd just mention it anyway.

    It accepts any value and lets you access properties (or call methods) on that method. If null was given then it will return null for any property or method call.

    You use it like this:

    1.     $a = null;
    2.     $b = new Model();
    3.  
    4.     optional($a)->doSomething(); // null
    5.     optional($a)->someProperty; // null
    6.  
    7.     optional($b)->doSomething(); // calls doSomething on the Model object
    8.     optional($b)->someProperty; // the someProperty on the model object (if it exists)

    It is useful when you don't know if something will have a value or not. I have used it in the past when dealing with an Eloquent row's relation - it might not exist. Rather than wrap some code in if/else, you can do something like this:

    1.     $blogPost = BlogPost::first();
    2.     echo "<div class='author_box'>"
    3.          . optional($blogPost->author)->name
    4.         . "</div>";

    (simplified example... but it would either print the author name, or nothing)

    Do you use View Composers?

    View composers will bound data to a view every time that view file is rendered.

    "View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location."

    An example snippet would take up too much code in this article, so please go to the official docs to see more.

    Use array_wrap() to ensure that you are working with an array

    Sometimes you need to work with an array, but the supplied data might be a different data type - in which case you will want to wrap it in an array (it will be the single element in the array). This is simple to do:

    1. // could return an array, could return a string, etc. It could be anything
    2. $value = foo();
    3.  
    4. if (!is_array($value)) {
    5.     // if it turns out it isn't an array, wrap it in an array
    6.     $value = [$value];
    7. }
    8.  
    9. // now you can be sure you have an array, so do whatever you need:
    10. foreach($value as $row) { /* ... */ }

    And although the snippet above could be simplified (and written on one line), it is a bit neater to use Laravel's helper function array_wrap()

    1. // could return an array, could return a string, etc. It could be anything
    2. $value = array_wrap(foo());
    3.  
    4. // now you can be sure you have an array, so do whatever you need:
    5. foreach($value as $row) { /* ... */ }

    More examples:

    1. $value = array_wrap(['a','b']); // ['a', 'b'] (no change)
    2. $value = array_wrap('asdf'); // ['asdf'] (wrapped the string in an array)
    Eloquent: Add data to the output from toArray() (or toJson())

    Let's say you have a User model, and you know that the user with ID of 1 is admin. When you do a toArray() or export it to JSON, you might want to output something to indicate if it is an admin. With a little bit of code you can get Eloquent to automatically add this.

    You can do something like this:

    1. class User extends Model
    2. {
    3.  
    4.     // append the 'is_admin' attribute when doing toArray()/toJson()...
    5.     protected $appends = ['is_admin'];
    6.  
    7.     // and this is where it will get that data from:
    8.     public function getIsAdminAttribute()
    9.     {
    10.         // returns a bool, true if user.id === 1
    11.         return $this->attributes['id'] === 1;
    12.     }
    13. }

    Note: This assumes that there was no is_admin column on the database table that this row came from. Otherwise it would have been included anyway.

    You can also decide to append data at run time like this:

    1. return $user->append('is_admin')->toArray();
    2.  
    3. // or
    4.  
    5. return $user->setAppends(['is_admin'])->toArray();

    See more here.

    Pluralise strings with str_plural()

    I think that this function is quite commonly used - it is quite a handy and useful function.

    Sometimes you need to output something like "5 active posts" (plural), or "1 active post" (singular). You can write some quick if/else logic and handle it that way. Or use str_plural().

    1. $my_bottles = [1,2,3];
    2. echo "I have " . count($my_bottles). " " . str_plural('bottle', count($my_bottles));
    3.  
    4. $your_bottles = [1];
    5. echo "You have " . count($your_bottles). " " . str_plural('bottle', count($your_bottles));

    The above would output 'I have 3 bottles' (plural!) and 'You have 1 bottle' (singular!). The function handles uncountable words (see the Pluralizer class) such as 'sheep', 'softaware' so you won't end up with 'sheeps' and 'softwares'. But of course it isn't perfect so you should always check it works correctly with your input. And it only handles English words correctly. I did assume that there might be some extensions to this function to support other languages, but I was unable to find any.

    There is also the opposite function: str_singular()

    1. $singular = str_singular('cars');
    2.  
    3. // car
    4.  
    5. $singular = str_singular('children');
    6.  
    7. // child

    Ok, that's it!

    I'm almost certain that most Laravel developers will be aware of a bunch of the above tips (maybe all of them). Almost all of the above tips were directly in the official docs. But maybe some people will find a new feature of Laravel from the list above.

    Whenever I start a new language, framework or library I always try and skim through the documentation, use it, then give it a full read. I'm sure I quickly forget most of what I read, but there are always lots of features in big libraries/frameworks which are underused.

    Please let me know in the comments if there are any useful features of Laravel that you think are underused, that I've not mentioned. I'm sure there are tons!

    webdevetc profile pic
    webdevetc

    I am a 29 year old freelance backend web developer from London, mostly focusing on PHP and Laravel lately. This is my site - I mostly write about PHP here. Contact me here (especially for any contracting jobs early 2019 in London ;) ).

    Leave a Comment