Extend Blueprint class? - php

I want to override the timestamps() function found in the Blueprint class. How can I do that?
e.g.,
public function up() {
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username')->unique();
$table->string('password');
$table->string('email');
$table->string('name');
$table->timestamps(); // <-- I want this to call my method instead of the one found in the base Blueprint class
});
}

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.

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();
});
}
// ....
}

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.

Related

Laravel container returning multiple Singleton instances

I've created a CustomProvider, added it to the app.php array of providers and registered a class as singleton:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\ReserveCart;
class CustomProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->singleton('App\ReserveCart', function($app){
return new ReserveCart;
});
}
}
but everytime I request for the object with $rc = resolve('App\ReserveCart'); it keeps giving me different instances of the object instead of a single one (I've done some echo tracking).
Also tried passing the dependency to methods acording to Laravel Documentation. e.g
public function foo(App\ReserveCart $rc){
//
}
but the issue persists.
Is the output below same ?
$rc = resolve('App\ReserveCart');
$rc1 = resolve('App\ReserveCart');
dd(spl_object_hash($rc), spl_object_hash($rc1));

Automatically Change Model Value If Another Value Updates

With Laravel & Eloquent, if a column called status changes its value to "complete," for example, is it possible to automatically change the value of another column (issue_id) to NULL?
I was wondering about the set attribute or intercepting the save() method, but not sure which is best.
You could make use of Observers.
For example, to observe the Issue model, you could generate an Observer as such:
php artisan make:observer IssueObserver --model=Issue
This will produce an observer where you could listen to many model events.
<?php
namespace App\Observers;
use App\Issue;
class IssueObserver
{
/**
* Handle the Issue "updating" event.
*
* #param \App\Issue $Issue
* #return void
*/
public function updating(Issue $issue)
{
if($issue->status == 'complete') {
$issue->issue_id = null;
}
}
}
To register the Observer, you would need to add this to AppServiceProvider#boot()
<?php
namespace App\Providers;
use App\Issue;
use App\Observers\IssueObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Issue::observe(IssueObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
You could also just do this in your App/Issue model.
public static function boot()
{
parent::boot();
static::updating(function ($issue) {
if($issue->status == 'complete') {
$issue->issue_id = null;
}
})
}
Obviously, you would need to listen on the events that suit your needs. This is just an example. You could take a look at all the available model events here.

Laravel / Eloquent: how to hook into the deletion process?

When a record gets deleted from my_items_table I want to insert the record into my_items_table_archive.
I could do this on each Controller, but would prefer to hook into the Eloquent model.
Is there anything like this?
Pseudocode:
class MyItem extends Model {
protected function beforeDelete($record) {
MyItemArchive::create($record); // add record the archive
return true; // continue deletion of $record
}
}
Any idea? Thanks!
Yes, there is something similar to your pseudocode.
You can utilise Eloquent Events
A good example of this can be seen below:
protected $dispatchesEvents = [
'deleted' => UserDeleted::class,
'deleting' => UserDeleting::class
];
The class in question just needs to adhere to / Follow: Listeners
You can also use Eloquent Observers / the observer pattern to achieve a similar result.
Let me know how you get on!
First of all create a new Observer using
php artisan make:observer MyItemObserver
Then
<?php
namespace App\Observers;
class MyItemObserver
{
public function deleting(MyItem $myItem)
{
/// insert new record here
}
}
Now you in your appServiceProvider
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
MyItem::observe(MyItemObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
Now your obverserver will be hooked to Model Events.
Hope this helps.
As described in the official documentation you have two choices using the events. The first one is creating an observer like this:
class MyModelObserver
{
/**
* Listen to the Model deleting event.
*
* #param User $user
* #return void
*/
public function deleting(User $user)
{
// HERE YOUR CODE TO TRANSFER THE MODEL
}
}
Than you have to register it on your AppServiceProvider
public function boot {
MyModel::observe(MyModelObserver::class)
}
Otherwise you can add these events in your model by generating the specific class:
protected $dispatchesEvents = [
'deleting' => MyModelDeletingEvent::class,
];
Anyway if you're using a version of laravel lower than 5.4 you should check the documentation for the specific implementation, since the $dispatchesEvents is not available as variable.

phinx migration 'up' vs. 'change' function

I have used phinx migration and used it's 'up' and 'change' functions but i did'nt notice any difference between them. Below is my migration file.
<?php
use Phinx\Migration\AbstractMigration;
class MediaManager extends AbstractMigration
{
public function up()
{
if($this->hasTable('uss_dish')){
$dish = $this->table('uss_dish');
$dish -> addColumn('coordinates','text',array('after' => 'district_id','default' => NULL))
-> save();
}
}
public function change()
{
if($this->hasTable('uss_dish')){
$dish = $this->table('uss_dish');
$dish -> addColumn('coordinates','text',array('after' => 'district_id','default' => NULL))
-> save();
}
}
}
Can anybody tell me the difference between these two functions ? Thanks in advance.
Phinx 0.2.0 added a new feature called reversible migrations. You can implement reversible migrations using change method. If you have implemented change method, you don't really need to write down or up methods. While migrating up your logic in change method will be executed and Phinx will figure out how to migrate down automatically for you.
Reversible migrations are helpful when you are defining table structure etc.
Example (Reversible Migrations using Change Method)
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
// create the table
$table = $this->table('user_logins');
$table->addColumn('user_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
/**
* Migrate Up.
*/
public function up()
{
}
/**
* Migrate Down.
*/
public function down()
{
}
}
Example (Same migration as above without using change method)
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
/**
* Migrate Up.
*/
public function up()
{
// create the table
$table = $this->table('user_logins');
$table->addColumn('user_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
/**
* Migrate Down.
*/
public function down()
{
$this->dropTable('user_logins');
}
}
Read more about Reversible Migrastions Phinx.

How to seed database migrations for laravel tests?

Laravel's documentation recommends using the DatabaseMigrations trait for migrating and rolling back the database between tests.
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
However, I've got some seed data that I would like to use with my tests. If I run:
php artisan migrate --seed
then it works for the first test, but it fails subsequent tests. This is because the trait rolls back the migration, and when it runs the migration again, it doesn't seed the database. How can I run the database seeds with the migration?
All you need to do is make an artisan call db:seed in the setUp function
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
// seed the database
$this->artisan('db:seed');
// alternatively you can call
// $this->seed();
}
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
ref: https://laravel.com/docs/5.6/testing#creating-and-running-tests
With Laravel 8, if you're using the RefreshDatabase trait you can invoke seeding from your test case using below:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
// Run the DatabaseSeeder...
$this->seed();
// Run a specific seeder...
$this->seed(OrderStatusSeeder::class);
$response = $this->get('/');
// ...
}
}
see docs for more information/examples:
https://laravel.com/docs/8.x/database-testing#running-seeders
It took me some digging to figure this out, so I thought I'd share.
If you look at the source code for the DatabaseMigrations trait, then you'll see it has one function runDatabaseMigrations that's invoked by setUp which runs before every test and registers a callback to be run on teardown.
You can sort of "extend" the trait by aliasing that function, re-declare a new function with your logic in it (artisan db:seed) under the original name, and call the alias inside it.
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends TestCase
{
use DatabaseMigrations {
runDatabaseMigrations as baseRunDatabaseMigrations;
}
/**
* Define hooks to migrate the database before and after each test.
*
* #return void
*/
public function runDatabaseMigrations()
{
$this->baseRunDatabaseMigrations();
$this->artisan('db:seed');
}
/**
* A basic functional test example.
*
* #return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
I know this question has already been answered several times, but I didn't see this particular answer so I thought I'd throw it in.
For a while in laravel (at least since v5.5), there's been a method in the TestCase class specifically used for calling a database seeder:
https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestCase.html#method_seed
with this method, you just need to call $this->seed('MySeederName'); to fire the seeder.
So if you want this seeder to fire before every test, you can add the following setUp function to your test class:
public function setUp()
{
parent::setUp();
$this->seed('MySeederName');
}
The end result is the same as:
$this->artisan('db:seed',['--class' => 'MySeederName'])
or
Artisan::call('db:seed', ['--class' => 'MySeederName'])
But the syntax is a bit cleaner (in my opinion).
With Laravel 8, the RefreshDatabase is now looking for a boolean property called "seed".
/**
* Illuminate\Foundation\Testing\RefreshDatabase
* Determine if the seed task should be run when refreshing the database.
*
* #return bool
*/
protected function shouldSeed()
{
return property_exists($this, 'seed') ? $this->seed : false;
}
Simply give your test class the protected property $seed and set it to true if you wish to seed.
class ProjectControllerTest extends TestCase
{
protected $seed = true;
public function testCreateProject()
{
$project = Project::InRandomOrder()->first();
$this->assertInstanceOf($project,Project::class);
}
The nice part about this method is that individual tests won't seed everytime they are ran. Only seed necessary test will build the database.
If you're using the RefreshDatabase testing trait:
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, RefreshDatabase {
refreshDatabase as baseRefreshDatabase;
}
public function refreshDatabase()
{
$this->baseRefreshDatabase();
// Seed the database on every database refresh.
$this->artisan('db:seed');
}
}
Here is an alternate solution, in case you prefer to bypass Artisan's native DatabaseMigrations and seeder/migration methods. You can create your own trait to seed your database:
namespace App\Traits;
use App\Models\User;
use App\Models\UserType;
trait DatabaseSetup
{
public function seedDatabase()
{
$user = $this->createUser();
}
public function createUser()
{
return factory(User::class)->create([
'user_type_id' => function () {
return factory(UserType::class)->create()->id;
}
]);
}
public function getVar() {
return 'My Data';
}
}
Then call it in your test like this:
use App\Traits\DatabaseSetup;
class MyAwesomeTest extends TestCase
{
use DatabaseSetup;
use DatabaseTransactions;
protected $reusableVar;
public function setUp()
{
parent::setUp();
$this->seedDatabase();
$this->reusableVar = $this->getVar();
}
/**
* #test
*/
public function test_if_it_is_working()
{
$anotherUser = $this->createUser();
$response = $this->get('/');
$this->seeStatusCode(200);
}
}

Categories