Installation:
composer require laragear/meta-model
Publish the config (if needed) with:
php artisan vendor:publish --provider="Laragear\MetaModel\MetaModelServiceProvider"
Extend Your Model:
Add the HasCustomization trait to your package model:
use Laragear\MetaModel\HasCustomization;
class MyPackageModel extends Model
{
use HasCustomization;
}
Define Base Migration:
Override migration() to return a CustomMigration instance:
protected static function migration(): string
{
return CustomMigration::make(function (Blueprint $table) {
$table->id();
$table->string('title');
$table->timestamps();
});
}
First Use Case:
Let third-party developers customize your model by publishing their own migrations or model extensions in a config/metamodel.php file:
return [
'models' => [
'Vendor\Package\Models\MyPackageModel' => [
'customizations' => [
'add_column' => [
'type' => 'migration',
'data' => [
'column' => 'custom_field',
'type' => 'text',
],
],
],
],
],
];
Define Base Schema:
Use CustomMigration::make() to encapsulate your default schema logic. This ensures customizations are applied after your base schema.
Expose Customization Hooks:
Provide clear documentation for developers on how to extend your model via metamodel.php. Example:
// In your package's docs:
// To add a custom field to MyPackageModel:
// config/metamodel.php
// 'customizations' => [
// 'add_column' => [
// 'type' => 'migration',
// 'data' => ['column' => 'slug', 'type' => 'string'],
// ],
// ]
Dynamic Migration Generation:
Override getCustomMigration() in your model to merge base and custom migrations:
public function getCustomMigration()
{
return CustomMigration::make(function (Blueprint $table) {
// Base schema
$table->id();
$table->string('title');
})
->mergeCustomizations();
}
Validation Layer:
Add validation in boot() to ensure customizations are safe:
protected static function boot()
{
parent::boot();
if (static::hasCustomizations()) {
static::validateCustomizations();
}
}
Extend Models:
Publish customizations in config/metamodel.php:
return [
'models' => [
'Vendor\Package\Models\MyPackageModel' => [
'customizations' => [
'scopes' => [
'active' => \Vendor\Package\Scopes\ActiveScope::class,
],
'mutators' => [
'title' => function ($value) {
return strtoupper($value);
},
],
],
],
],
];
Run Migrations:
Use the metamodel:migrate Artisan command to apply customizations:
php artisan metamodel:migrate
Override Defaults:
Use mergeCustomizations() to override or extend existing behavior:
// Override a column type
CustomMigration::make(function (Blueprint $table) {
$table->string('title', 255); // Default
})
->mergeCustomizations(['title' => 'text']);
Migration Order:
Custom migrations run after your base schema. Ensure your base schema includes all required columns (e.g., id()) before customizations are applied.
Schema Conflicts:
Customizations may introduce schema conflicts (e.g., duplicate columns). Validate customizations in boot():
public static function validateCustomizations()
{
$customizations = config("metamodel.models." . static::class);
if (isset($customizations['customizations']['add_column'])) {
foreach ($customizations['customizations']['add_column'] as $column) {
if (static::hasColumn($column['column'])) {
throw new \RuntimeException("Column {$column['column']} already exists.");
}
}
}
}
Caching Issues: Clear configuration cache after adding customizations:
php artisan config:clear
Octane Compatibility: If using Laravel Octane, ensure migrations are run in the correct queue to avoid race conditions during hot reloads.
Log Customizations:
Add debug logging to metamodel.php:
'debug' => [
'log_customizations' => true,
],
This will log all applied customizations to storage/logs/laravel.log.
Inspect Migrations:
Use php artisan metamodel:inspect to preview pending customizations before migration.
Rollback Customizations:
To revert customizations, manually rollback migrations or use a metamodel:rollback command (if implemented):
php artisan metamodel:rollback --model=Vendor\Package\Models\MyPackageModel
Custom Migration Types:
Extend Laragear\MetaModel\CustomMigration to support additional customization types (e.g., add_index, add_foreign):
class ExtendedCustomMigration extends CustomMigration
{
public function addForeign(string $column, string $table, string $foreignColumn)
{
$this->customizations['foreign'][] = compact('column', 'table', 'foreignColumn');
}
}
Dynamic Model Events:
Use registerCustomEvents() to dynamically bind events based on customizations:
protected static function registerCustomEvents()
{
if (static::hasCustomization('observers')) {
static::observe(config("metamodel.models." . static::class . ".observers"));
}
}
Package-Specific Config: Allow users to override default customization paths via config:
// config/metamodel.php
'paths' => [
'customizations' => database_path('customizations'),
],
Testing Customizations:
Use Laravel’s RefreshDatabase trait to test customizations in isolation:
use Illuminate\Foundation\Testing\RefreshDatabase;
class MyPackageModelTest extends TestCase
{
use RefreshDatabase;
public function test_customizations()
{
config(['metamodel.models.Vendor\Package\Models\MyPackageModel.customizations' => [...]]);
$this->artisan('metamodel:migrate')
->expectsOutput('Migration applied successfully.')
->assertExitCode(0);
}
}
How can I help you explore Laravel packages today?