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()
toDB::getSchemaBuilder()
because Laravel automagically forward the method call to the connection instance.Each call to the
Schema
Facade already creates a newSchema\Builder
instance, as can be seen in thegetFacadeAccessor()
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.