My 3 Favourite Things From Laravel 7

March 11, 2020
Written by

My 3 Favourite Laravel 7 Features

Laravel 7 has released. And like any proper major release, it's packed to the rafters with new and exciting features. It's time to take a look at three of my favourite new features, and, if we're lucky, there might be a bonus feature at the end.

You’ll need some existing knowledge of PHP and a little Laravel knowledge to follow along. You can find the sample code used in this article on GitHub.

Custom Casting with Eloquent

Eloquent, Laravel's object relationship mapper is a useful tool for moving data between our database and our code. We could do some sensible type conversions using Eloquent’s Mutators already. For example, add any field to the $dates property of our model and it is automatically converted into a Carbon date object for us.

Custom Casts take this a step further by helping us to decouple how our data looks in our database and our application. Often we'll want to store our data one way, and interact with it in our application another. Custom casts allow us to automatically convert data between the database state and the application state on read and write.

Let's assume we have a transactions table described by this migration:

database/migrations/create_transactions_table.php

<?php

Schema::create('transactions', function (Blueprint $table) {
    $table->uuid('uuid');
    $table->integer('amount_primary');
    $table->string('amount_currency');
    $table->integer('amount_secondary');
    $table->char('amount_separator');
    $table->char('amount_symbol');
    $table->date('date');
    $table->id();
    $table->timestamps();
});

We're storing some information about the cost of the transaction in several fields, the primary and secondary values, the currency's code, separator and symbol. This format is ok for the database, but it would be better if we could interact with currency amounts in our codebase using an Amount value object.

app/ValueObject/Amount.php

<?php

namespace App\ValueObject;

class Amount
{
    public int $primary;
    public int $secondary;
    public string $separator;
    public string $symbol;
    public string $currency;

    public function __toString(): string
    {
        return $this->symbol . $this->primary . $this->separator . $this->secondary;
    }
}

We're using a simple object that stores our data in properties and can be cast to a string for display. It makes sense to represent monetary amounts in value objects, and the new custom casting feature from Laravel 7 makes it so much easier for us to do just that.

Casts are PHP classes that implement the CastsAttributes interface that prescribes get and set methods. The get method is called when we want to convert from the database to our application. set is the opposite; it's called when we want to change from our application to the database.

In our case we can create a cast to handle our money amounts:

app/Cast/AmountCast.php

<?php

namespace App\Cast;

use App\ValueObject\Amount;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class AmountCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): Amount
    {
        $amount = new Amount();
        $amount->primary = $attributes['amount_primary'];
        $amount->secondary = $attributes['amount_secondary'];
        $amount->symbol = $attributes['amount_symbol'];
        $amount->currency = $attributes['amount_currency'];
        $amount->separator = $attributes['amount_separator'];
        return $amount;
    }

    /** @var Amount $model */
    public function set($model, string $key, $value, array $attributes): array
    {
        return [
            'amount_primary' => $model->primary,
            'amount_secondary' => $model->secondary,
            'amount_symbol' => $model->symbol,
            'amount_currency' => $model->currency,
            'amount_separator' => $model->separator,
        ];
    }
}

We can see how our get creates a new Amount value object and populates it with the data from the database, while set converts the data from that value object into a key/value pair ready for the database.

To tell Eloquent to use this cast, we add a new $casts array property to our Transaction model where the key is the property name, and the value is the cast to use.

app/Transaction.php

<?php

namespace App;

use App\Cast\AmountCast;
use Illuminate\Database\Eloquent\Model;

class Transaction extends Model
{
    protected $casts = [
        'amount' => AmountCast::class
    ];
}

We can test this works by creating a simple route:

routes/web.php

<?php

Route::get('/casts', function () {
    $transaction = Transaction::where('id', 1)->first();
    return (string)$transaction->amount;
});

If we hit /casts in our application, we'll see the __toString method of the Amount object being run, which proves that our value object is being created by eloquent.

I'm always a fan of using value objects rather than arrays in our applications. Objects are so much less error-prone as we get completion by our editor, and also can be made immutable so we know the state can't be changed in places where it shouldn't. This is a great addition to help us to have better-architected applications.

Blade Components

Blade components let us render isolated atomic areas of our front-end with reusable components. They look a lot like components from a front-end framework like React or Vue but only use the Blade templating engine and are entirely server-side. I like these for creating reusable code to solve repeated problems.

It's common in web applications that we need to render errors from user interactions, and these are a great example of components we'll need to reuse around our front-end. We've been able to do this previously using subviews, but the new component architecture simplifies the process and brings it in line with existing front-end ideas.

We can create a new component by using the Artisan command-line tool from the root of our project:

php artisan make:component Error

Blade components need to extend the Component abstract class and should implement the render method. Let's take a look at our error component class.

app/View/Components/Error.php

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Error extends Component
{

    public string $message;

    public function __construct(string $message)
    {
        $this->message = $message;
    }

    public function type(string $type = null): string
    {
        return $type ?? 'warning';
    }

    public function render(): View
    {
        return view('component.error');
    }
}

A bit is going on here that's worth investigating. Any constructor arguments are handled from one of two places. In our example, we're expecting a string parameter which gets populated by the props of the template that calls this component; we'll see this later. We can also include class names as arguments, and they'll be fulfilled automatically from the container.

We're using the view global function to render our sub-component template. Any public properties or methods of the component class are automatically made available in any view template you call.

resources/views/component/error.blade.php

<div class="alert alert-{{ $type() }}" {{ $attributes }} role="alert">
    {{ $message }}
</div>

The $attributes property is automatically passed down from the parent template, while the $type is a method of our component class that Blade has automatically made available. The $messages property came in as a prop from our calling HTML as a property:

resources/views/components.blade.php

<x-error message="There's been a big problem" class="ignored" />

The message prop is passed into the component class as a constructor argument that's set as a public property that's available in the rendered component. Phew.

Example error message showing a error box reading "There&#39;s been a problem"

Anything that helps us to remove duplication in our code and allows us to edit things in one place is a win for me. I'll be using this in my code to keep changes to one place.

New HTTP Client Wrapper

Guzzle is a fantastic HTTP client and can do any and every HTTP request you'll ever need to make. Unfortunately, the API that makes it so powerful also makes it difficult to remember how to use it to make even the simplest requests. Laravel 7's HTTP client is a convenience wrapper that lets us make the most common HTTP requests predictably and memorably.

<?php

Route::get('/http-client', function(){
    $response = Http::get('http://www.recipepuppy.com/api/?i=garlic,bread');
    return $response['results'][array_rand($response['results'])];
});

Making a plain GET request is a one-line call, but when we want to do more complex things, it gets easier. I've lost count of the number of times I've had to look up the options array key to send data as a form post. Using the new Laravel 7 wrapper, we can call the asForm method and then add the form fields as the second parameter.

The wrapper also gives us more comfortable ways to figure out what's going on with the responses to our requests. Unlike Guzzle, Laravel's client doesn't throw exceptions when it encounters a non-200 status code; instead, it makes the status available via helper methods like successful or ok. We can turn back on the default Guzzle exception behaviour if that's what we'd prefer.

The HTTP Client comes with some helpful mocking methods out of the box, so we don't have to make real requests in our tests:

<?php

public function testHttpRequestToApiReturns200()
{
    Http::fake([
        'www.recipepuppy.com/*' => Http::response(['recipe'], 302),
    ]);
    self::assertEquals(302, Http::get('http://www.recipepuppy.com/api/?i=garlic,bread')->status());
}

All in all, I think that simplifying such a common task will save plenty of developers’ time.

BONUS: New Test Runner

Laravel 7 comes with a new test-runner that we can run using the command:

php artisan test

Example output from Laravel 7 test runner

We can enjoy the swish new visuals, plus the runner halts on the first failure by default.

Get Upgrading!

There are plenty more new and exciting things in Laravel 7 and upgrading is not a huge task, so hopefully, this taster has given you the impetus to upgrade. Let me know what's your favourite feature in Laravel 7 on Twitter or in the comments below.

I can't wait to see what you build.

  • Email: ghockin@twilio.com
  • Twitter: @GeeH