My team at Spatie is currenlty building Mailcoach, a solution to self host your e-mail newsletter. Mailcoach can be used a stand alone software or as a Laravel package. Subscribe now at Mailcoach to get a notification as soon as we release it.

The mixin PHP DocBlock

Original – by Freek Van der Herten – 5 minute read

When using PHP, you've probably used DocBlocks. They can be used to add additional information that can't be inferred by looking at the source code alone. DocBlocks can be used by IDEs, like PhpStorm, to improve autocomplete suggestions.

In this blogpost, I'd like to highlight a not so well known DocBlock: mixin.

A first example of a DocBlock

Before looking at a mixin DocBlock, let's first get a feel of how DocBlocks can make autocompletion better.

/**
 * @return \App\Models\Post[]
 */
public function allPosts(): array
{
    // ...
}

With regular type hints, you can only specify that the function returns an array. The DocBlock to signals that the function returns an array of \App\Models\Post objects.

When you loop through the value returned by allPosts an IDE knows which kind of object is inside the array and can provide autocompletion for that object.

A theoretical first example

The mixin DocBlock allows you to signal that all properties and methods of another class are available on the class where the mixin is applied upon.

Here's some contrived code to illustrate it:

class ClassA
{
    public function methodOfClassA(): string
    {
        return 'This is classA';
    }
}

/** @mixin ClassA */
class ClassB
{
    public function methodOfClassB(): string
    {
        return 'This is classB';
    }

    public function __call($name, $arguments)
    {
        (new ClassA())->$name(...$arguments);
    }
}

If you call a method a method on classB that isn't available, it will defer to calling classA. The intention here is to make every method that is available on classA part of classB as well.

Thanks to that mixin DocBlock, an IDE knows that intention as well and will also suggest functions of classA when calling methods on classB

Screenshot

A practical example

Let's take a look at some practical example of how this can be used.

laravel-medialibrary is a package that can be used to associate Eloquent models with media. One of its features is the ability to generate conversions whenever an image is being associated with a model.

A conversion can be defined on a model like this.

public function registerMediaConversions(Media $media = null)
{
    $this->addMediaConversion('thumb')
          ->width(368)
          ->height(232)
          ->sharpen(10);
}

So you first call addMediaConversion and then you can call any manipulations, like width and height, you want.

Behind the scenes, the addMediaConversion will return an instance of Spatie\MediaLibrary\Conversion\Conversion. This class is responsible for handling the registration of the image conversions. It does not directly handle image manipulations like width or height. Those are handled by Spatie\Image\Manipulations. Whenever we call a function on Conversion that doesn't exist on itself, it will defer the call to Manipulations. The deferring is done by this piece of code.

public function __call($name, $arguments)
{
    if (! method_exists($this->manipulations, $name)) {
        throw new BadMethodCallException("Manipulation `{$name}` does not exist");
    }

    $this->manipulations->$name(...$arguments);

    return $this;
}

With this code in place, it will all work, but the developer experience won't be great. The IDE will offer no autocompletion when typing $this->addMediaConversion('thumb')->. To make autocompletion work we added this mixin at the top of the Conversion class.

/** @mixin \Spatie\Image\Manipulations */

That mixin allows the IDE to add autocompletion, so developers can just pick the image manipulation method they want.

Screenshot

Using a mixin DocBlock to autocomplete Laravel API resources

In the previous example the mixin DocBlock was used within package code, but you can use it inside projects too. At Spatie, they are often using in Laravel's API resources.

An API resource is a class that maps the attributes of an model to the properties on an API response. Here's an example of such a resource.

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'avatar_url' => $this->getFirstMediaUrl('avatar'),
        ];
    }
}

In an API resource in Laravel, calling a property on $this will get the property of the underlying model. But as it stands, typing $this-> will not offer autocompletion of the model attributes.

We must perform two steps to get autocompletions. First, we must let our IDE know which attributes there are on the model. We can use the excellent barryvdh/laravel-ide-helper for this. With the package installed, executing php artisan ide-helper:models will generate a file that lets your IDE know which properties there are available on a model.

The second and final step to get autocompletion working on API resource is adding a mixin DocBlock on it.

/** @mixin \App\User */
class UserResource extends JsonResource

With this in place, autocompletion will work.

Screenshot

Closing thoughts

A mixin DocBlock can be used to hint that methods from another class are available on the class where the DocBlock is applied on.

There is no formal standard of which DocBlocks are available. The de facto standard is this list on the documentation of phpDocumentor. You'll notice that the mixin DocBlock isn't there. So be aware that this DocBlock probably doesn't work in any IDE. In PhpStorm, it works perfectly.

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

You can comment on this post by replying to this tweet.
Grzegorz Gutkowski liked on 17th October 2019
Claudson Martins liked on 16th October 2019
Ruslan liked on 15th October 2019
Neil Carlo Faisan Sucuangco liked on 15th October 2019
Taste The Code liked on 15th October 2019
Nemanja Ivankovic liked on 15th October 2019
Grebenita Vladin T liked on 15th October 2019
Roman Pronskiy liked on 15th October 2019
Freek Van der Herten replied on 15th October 2019
Damn, busted :-)
Freek Van der Herten replied on 15th October 2019
In the first real world example, it doesn't really make sense to put image manipulations in a trait, it's in a class (and even package) of it's own. In the second real world example (API resources) you don't have control over the implementation of the framework.
Gav Chap replied on 15th October 2019
This seems like an obfuscated trait to me. Why not use traits? Or am I missing something?
Grammar Police replied on 15th October 2019
In this update, it could be better if you, @freekmurze, had said “package) of [its] own” instead. ‘It's’ means ‘it is’ or ‘it has’, but ‘its’ is possessive.
oluwajubelo loves VueJS 🚨 liked on 14th October 2019
Eric Junker replied on 14th October 2019
If Laravel added a mixin DocBlock to the Facade classes then would PHPStorm be able to autocomplete methods? If so, might be a good PR to Laravel.
Spatie retweeted on 14th October 2019
이현석 Hyunseok Lee liked on 14th October 2019
Beater liked on 14th October 2019
Volkan Metin liked on 14th October 2019
Johan Alvarez liked on 14th October 2019
Niels liked on 14th October 2019
Gaurav Makhecha liked on 14th October 2019
Gaurav Makhecha replied on 14th October 2019
Always wondered how this works, thanks.
Daniel Puente liked on 14th October 2019
Rias Van der Veken liked on 14th October 2019
Sendoa Portuondo liked on 14th October 2019
Robert Boes liked on 14th October 2019
Jino Antony liked on 14th October 2019
Ryan Chandler liked on 14th October 2019
Ⓜ️ fuz liked on 14th October 2019
Christian Leo-Pernold liked on 14th October 2019
pxgamer liked on 14th October 2019
Muhammad Sumon Molla Selim liked on 14th October 2019
Elie Andraos liked on 14th October 2019
Elie Andraos retweeted on 14th October 2019
Clément Baconnier liked on 14th October 2019
Isern Palaus liked on 14th October 2019
. liked on 14th October 2019
David Cottila liked on 14th October 2019