esslassi / metable
Laravel MetaData for Eloquent Models
Requires
- php: >=7.3
- ext-json: *
- illuminate/database: ^8.0|^9.0|^10.0|^11.0
- illuminate/events: ^8.0|^9.0|^10.0|^11.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0
Requires (Dev)
- phpunit/phpunit: ^9.5|^10.5
README
Laravel MetaData for Eloquent Models
Installation
Composer
Laravel can be installed on laravel 9.x
or higher.
Run:
composer require esslassi/metable
Register the package's service provider in config/app.php:
'providers' => [ ... Esslassi\Metable\MetableServiceProvider::class, ... ];
You should publish the migration and the config/metable.php config file with:
php artisan vendor:publish --provider="Esslassi\Metable\MetableServiceProvider::class"
You can customize the default meta table (default: meta
) in the config/metable.php:
return [ /* * * You can customize default table name by relacing 'meta' to your own * */ 'tables' => [ // default table for all models 'default' => 'meta' ] ];
Execute the migration command to create the meta table after the config and migration have been published and configured:
php artisan migrate
Configuration
Model Setup
Add the Metable trait to your models you need to be metable:
use Esslassi\Metable\Metable; class User extends Eloquent { use Metable; ... }
Default Model Attribute values
Additionally, you can specify default values by assigning an array called $defaultMetaValues
to the model. This configuration has two side effects:
- If a meta attribute does not exist, the default value will be returned instead of null.
- If you set a meta attribute to its default value, the row in the meta table will be removed, causing the default value to be returned as described in rule 1.
This is being the desired and expected functionality for most projects, but be aware that you may need to reimplement default functionality with your own custom accessors and mutators if this functionality does not fit your needs.
This functionality is most suited for meta entries that note exceptions to rules. For example: users re not admins (default value: not admin), nodes taken down for maintenance (default value: node up), etc. This means the table doesn't need to store data on every entry which is in the expected state, only those rows in the exceptional state, and allows the rows to have a default state upon creation without needing to add code to write it.
public $defaultMetaValues = [
'is_admin' => false,
];
Metable Proccess
Setting Content Meta
To set a meta value on an existing attribute or create new data:
- Fluent way: You can set meta values seamlessly just like you do with regular Eloquent models.
- Metable checks if the attribute belongs to the model; if it doesn't, it will access the meta model to append or set a new meta value.
$user = new User; $user->name = 'Esslassi Mohammed'; // model attribute $user->country = 'Morocco'; // meta data attribute $user->save(); // save model
You can use setMeta
to set or add a meta value
$user = new User; $user->name = 'Esslassi Mohammed'; $user->setMeta('country', 'Morocco'); $user->save();
Setting multiple metas:
$user = new User; $user->name = 'Esslassi Mohammed'; $user->setMeta([ 'country' => 'Morocco', 'city' => 'Fez' ... ]); $user->save();
Setting multiple metas with model columns:
$user = new User; $user->setAttributes([ 'name' => 'Esslassi Mohammed', 'country' => 'Morocco', 'city' => 'Fez' ... ]); $user->save();
Using standard create
method to create model:
If an attribute doesn't exists on model attributes it will added as meta values.
User::create([ 'name' => 'Esslassi Mohammed', 'country' => 'Morocco', 'city' => 'Fez' ... ]);
Removing Content Meta
As usual if you want to remove a meta value you can use unset
and the save the model:
$user = User::find(1); $user->name // model attribute unset($user->country) // remove meta on save $user->save();
Using removeMeta
:
$user = User::find(1); $user->removeMeta('country'); $user->save();
Removing multiple metas:
$user = User::find(1); $user->removeMeta(['country', 'city']); OR $user->removeMeta('country', 'city'); $user->save();
Deleting all meta at once:
$user = User::find(1); $user->deleteAllMeta();
Note: This function will delete all metas from database directly with no rollback available.
Checking for Metas
To see if a piece of content has a meta:
... if (isset($user->country)) { ... } ...
You can check if meta exists by hasMeta
method:
... if ($user->hasMeta('country')){ } ...
You may also check if model has multiple metas:
//By providing an array $user->hasMeta(['country', 'city']); // By pipe spliter $user->hasMeta('country|city'); // By comma spliter $user->hasMeta('country,city');
Note: It will return true only if all the metas exist.
If you provide a true
in second arg it will return true even if meta marked for deletion:
$user = User::find(1); $user->removeMeta('country'); $user->hasMeta('country', true); // Return true $user->hasMeta('country'); // Return false ...
Retrieving Meta
Fluent way, You can access meta data as if it is a property on your model. Just like you do on your regular eloquent models.
$user = User::find(1); $user->name; // Return Eloquent value $user->country; // Return meta value
You can retrieve a meta value by using the getMeta
method:
$user = $user->getMeta('country');
Or specify a default value, if not exists:
$user = $user->getMeta('country', 'Morocco');
Note: default values set in the
$defaultMetaValues
property take precedence over any default value passed to this method.
You can also retrieve multiple meta values at once and receive them as an collection.
// By comma $user = $user->getMeta('country,city'); // By pipe $user = $user->getMeta('country|city'); // By an array $user = $user->getMeta(['country', 'city']);
Disable Fluent Access
In some cases you want to disable fluent access or setting:
protected $disableFluentMeta = true;
By setting that property, you will no longer setting or accessing to meta value by fluent way:
$user = User::find(1); $user->country = 'Morocco';// This will not set meta, this action will call setAttribute() of model class. $user->country;// Here will not retrieve meta unset($user->country);// No action will take isset($user->country);// Will not check if meta exists
So the only way to set a meta is calling setMeta
or addMeta
methods
Retrieving All Metas
To fetch all metas associated with a piece of content, use the getMeta
without any params
$user = User::find(1); $metas = $user->getAllMeta();
Meta Clauses
Where Meta Clauses
You can use the meta query builder's whereMeta
method to add where
clauses to the meta query. The most basic call to the whereMeta
method requires three arguments: the name of the column, the operator (which can be any of the database's supported operators), and the value to compare against the column's value.
For instance, the following query retrieves users whose country meta key value is 'Morocco':
$users = User::whereMeta('country', '=', 'Morocco') ->get();
For convenience, if you want to verify that a column equals a given value, you can pass the value as the second argument to the whereMeta
method. The package will assume you want to use the =
operator:
$users = User::whereMeta('country', 'Morocco') ->get();
Or Where Meta Clauses
When chaining calls to the meta query builder's whereMeta
method, the clauses will be joined using the AND
operator. However, you can use the orWhereMeta
method to join a clause to the meta query using the OR
operator. The orWhereMeta
method accepts the same arguments as the whereMeta
method:
$users = User::whereMeta('country', 'Morocco') orWhereMeta('continent', 'Africa') ->get();
If you need to group an "or" condition within parentheses, this package doesn't support this option to do that just use Laravel where grouping:
$users = User::whereMeta('country', 'Morocco') ->orWhere(function (Builder $query) { $query->whereMeta('continent', 'Africa') ->whereMeta('competition', 'CAN'); }) ->get();
Where Meta Not Clauses
The whereMetaNot
and orWhereMetaNot
methods can be used to negate specific query constraints. For example, the following query excludes users who live in Morocco:
$users = User::whereMetaNot('country', 'Morocco') ->get();
Where Meta Not Clauses
The whereMetaIn
and orWhereMetaNotIn
methods testing if a meta in an array given:
$users = User::whereMetaIn('country', ['Morocco', 'Algeria', 'Egypt', 'Tunisia']) ->get();
Additional Where Clauses
whereMetaNull / orWhereMetaNull
The whereMetaNull method verifies that a meta's value is null:
$users = User::whereMetaNull('country') ->get();
whereMetaNotNull / orWhereMetaNotNull
The whereMetaNotNull method verifies that a meta's value is not null:
$users = User::whereMetaNotNull('country') ->get();
whereMetaHas / orWhereMetaHas
The whereMetaHas method verifies that a meta key exists:
$users = User::whereMetaNotNull('country') ->get();
whereMetaDoesntHave / orWhereMetaDoesntHave
The whereMetaDoesntHave method verifies that a meta key not exists:
$users = User::whereMetaDoesntHave('country') ->get();
whereInMetaArray / whereNotInMetaArray (MySQL Only)
The whereInMetaArray method verifies if given value is in meta value. The following example retrieves users whose 'Morocco' in meta key value:
$users = User::whereInMetaArray('countries', 'Morocco') ->get();
Note: This method is equals to Laravel's
whereJsonContains
but since value column's type is long text we couldn't work with it so we decided to create this method to help us with this problem.
Defining Relationships
Actually metable has one relationship so our trip in this section will be short.
One to One
Imagin that we have in our app three roles manager
, driver
, supervisor
but the only role must have a car is driver
and you can't set forign key user_id
to cars table because a car can be driven by one or more drivers:
users
id - integer
name - string
role - string
cars
id - integer
model - string
marque - string
So we decided to create an metable relationship calls MetaOne with initialization method hasMetaOne
:
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Esslassi\Metable\Relations\MetaOne; class User extends Model { /** * Get the car. */ public function car(): MetaOne { return $this->hasMetaOne(Car::class, 'car_id'); } }
The first argument passed to the hasMetaOne method is the name of the final model we wish to access, while the second argument is the name of the meta key.
$user = User::find(1); $car = $user->car;
Key Conventions
Basically metable automatically recognize the local key of the model but the meta key name is required. So no need to identify the local key. If you would like to customize the local key of the relationship, you may pass it as the third argument to the hasMetaOne method.
class User extends Model { /** * Get the car. */ public function car(): MetaOne { return $this->hasMetaOne( Car::class, 'car_id', // Meta key of the users meta... 'id' // Local key on the users table... ); } }