Extend Blueprint class?

I likewise wanted to solve this in an "automagic" way, so took inspiration from @WesleySmith's answer to implement a solution that involved overriding the base Schema facade that returns a customised Blueprint class.

However, as of Laravel 9, the protected static function getFacadeAccessor() method is now expected to return a string only, representing a lookup key in the service container. This change is documented in the Laravel 9 upgrade guide (search for "getFacadeAccessor"), and here's the relevant commit to the framework that made this alteration. The upgrade guide has this to say:

The getFacadeAccessor method must always return a container binding key. In previous releases of Laravel, this method could return an object instance; however, this behavior is no longer supported. If you have written your own facades, you should ensure that this method returns a container binding string.

For the Schema facade, this container binding key is the string db.schema (as seen here, although it hasn't been documented in the Facade Class Reference in the Laravel documentation yet).

Therefore my Schema class dispenses with the customised getFacadeAccessor method and relies on the parent class to return the db.schema key, and provides the logic for creating a custom schema builder using a static customizedSchemaBuilder method.

class Schema extends BaseSchema
{
    /**
     * Get a schema builder instance for a connection.
     *
     * @param string|null $name
     *
     * @return Builder
     */
    public static function connection($name): Builder
    {
        return static::customizedSchemaBuilder($name);
    }

    /**
     * Retrieves an instance of the schema `Builder` with a customized `Blueprint` class.
     *
     * @param string|null $name
     *
     * @return Builder
     */
    public static function customizedSchemaBuilder(string|null $name = null): Builder
    {
        /** @var Builder $builder */
        $builder = static::$app['db']->connection($name)->getSchemaBuilder();
        $builder->blueprintResolver(static fn($table, $callback) => new CustomBlueprint($table, $callback));
        return $builder;
    }
}

The logic to resolve the customised Builder that was previously in getFacadeAccessor should instead be included in your AppServiceProvider's register method:

/**
 * @return void
 */
public function register()
{
    $this->app->bind('db.schema', fn() => Schema::customizedSchemaBuilder());
}

You should now be able to use your customised Schema facade and Blueprint classes in your migrations using Laravel 9.


Just to add a few points to Marcel Gwerder's answer (which is already great):

  • You can shorten DB::connection()->getSchemaBuilder() to DB::getSchemaBuilder() because Laravel automagically forward the method call to the connection instance.

  • Each call to the Schema Facade already creates a new Schema\Builder instance, as can be seen in the getFacadeAccessor() method in the following files:

    • Support/Facades/Schema.php - Laravel 4.2
    • Support/Facades/Schema.php - Laravel 5.0-dev


(edit 2016-03-06)
A GitHub issue has been recently opened about this: #12539.


Marcel Gwerder's answer was a life saver. Like some of the users commented there, I wondered if this could be done more automagically. My goal was similarly to overwrite the timestamps method. After some tinkering, this is what I ended up with which is working for me:

I created a file at app/Classes/Database/Blueprint.php:

<?php

namespace App\Classes\Database;


use Illuminate\Support\Facades\DB;
use Illuminate\Database\Schema\Blueprint as BaseBlueprint;

class Blueprint extends BaseBlueprint
{
    /**
     * Add automatic creation and update timestamps to the table.
     *
     * @param  int  $precision
     */
    public function timestamps($precision = 0): void
    {
        $this->timestamp('created_at', $precision)->default(DB::raw('CURRENT_TIMESTAMP'));
        $this->timestamp('updated_at', $precision)->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
    }
}

I created a file at app/Facades/Schema.php

<?php

namespace App\Facades;

use App\Classes\Database\Blueprint;
use Illuminate\Database\Schema\Builder;
use Illuminate\Support\Facades\Schema as BaseSchema;

class Schema extends BaseSchema
{
    /**
     * Get a schema builder instance for a connection.
     *
     * @param  string|null  $name
     * @return Builder
     */
    public static function connection($name): Builder
    {
        /** @var \Illuminate\Database\Schema\Builder $builder */
        $builder = static::$app['db']->connection($name)->getSchemaBuilder();
        $builder->blueprintResolver(static function($table, $callback) {
            return new Blueprint($table, $callback);
        });
        return $builder;
    }

    /**
     * Get a schema builder instance for the default connection.
     *
     * @return Builder
     */
    protected static function getFacadeAccessor(): Builder
    {
        /** @var \Illuminate\Database\Schema\Builder $builder */
        $builder = static::$app['db']->connection()->getSchemaBuilder();
        $builder->blueprintResolver(static function($table, $callback) {
            return new Blueprint($table, $callback);
        });
        return $builder;
    }
}

Inside config/app.php I updated the alias for Schema as follows:

'aliases' => [
    'Schema' => App\Facades\Schema::class,
],

Now, in my migrations, like the below, when I call timestamps(), it calls my overwritten method.

<?php

use App\Classes\Database\Blueprint;
use \Illuminate\Database\Migrations\Migration;

class TimestampTest extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     * @throws \Throwable
     */
    public function up(): void
    {
        Schema::connection('mysql')->create('some_table', static function (Blueprint $table) {
            $table->string('some_column')->nullable();
            $table->timestamps();
        });

    }

    // ....
}

There is a new blueprintResolver function which takes a callback function which then returns the Blueprint instance.

So create your custom Blueprint class like this:

class CustomBlueprint extends Illuminate\Database\Schema\Blueprint{

    public function timestamps() {
        //Your custom timestamp code. Any output is not shown on the console so don't expect anything
    }
}

And then call the blueprintResolver function where you return your CustomBlueprint instance.

public function up()
{
    $schema = DB::connection()->getSchemaBuilder();

    $schema->blueprintResolver(function($table, $callback) {
        return new CustomBlueprint($table, $callback);
    });

    $schema->create('users', function($table) {
        //Call your custom functions
    });
}

I'm not sure if creating a new schema instance with DB::connection()->getSchemaBuilder(); is state of the art but it works.

You could additionally override the Schema facade and add the custom blueprint by default.