This Laravel package:
- Provides a fluent way to define structure / schema over objects
- Allows you to validate those objects in a consistent way
- Easily cast eloquent json attributes to objectModels for easy handling
- Reduces the number of meta tables in your app
This package requires
- PHP 8.1 and
- Laravel 8.0 or higher (including 10).
Install it:
composer require coder-at-heart/object-models
Extend the ObjectModel class and extend override the properties()
method
I usually create an app/ObjectModels
folder to store my models
Here's an example from the tests folder:
<?php
namespace CoderAtHeart\ObjectModel\Tests\Models;
use Carbon\Carbon;
use CoderAtHeart\ObjectModel\ObjectModel;
use CoderAtHeart\ObjectModel\Property;
/**
* @property string name
* @property int age
* @property string email
* @property Address home
* @property Address business
* @property Phone[] phone_numbers
* @property Carbon birthday
* @property Carbon alarm
* @property bool subscribed
* @property array friends
* @property Carbon[] important_dates
*/
class Person extends ObjectModel
{
public static function properties(): array
{
return [
Property::string('name')->required(),
Property::integer('age')->nullable(),
Property::email('email'),
Property::objectModel('home', Address::class),
Property::objectModel('business', Address::class),
Property::objectModelArray('phone_numbers', Phone::class),
Property::date('birthday'),
Property::time('alarm')->default('08:00:00'),
Property::bool('subscribed')->default(false),
Property::array('friends'),
Property::propertyArray('important_dates', Property::date('date')),
];
}
}
And the Address
namespace CoderAtHeart\ObjectModel\Tests\Models;
use CoderAtHeart\ObjectModel\ObjectModel;
use CoderAtHeart\ObjectModel\Property;
use CoderAtHeart\ObjectModel\Tests\ENUMs\Country;
/**
* @property string address_1
* @property string address_2
* @property string city
* @property string postcode
* @property Country country_code
*/
class Address extends ObjectModel
{
public static function properties(): array
{
return [
Property::string('address_1'),
Property::string('address_2'),
Property::string('city'),
Property::enum('country_code', Country::class)->default(Country::GB)->required(),
Property::string('postcode'),
];
}
}
The Country
ENUM
<?php
namespace CoderAtHeart\ObjectModel\Tests\ENUMs;
enum Country : string
{
case US = 'us';
case GB = 'gb';
}
And the Phone
:
<?php
namespace CoderAtHeart\ObjectModel\Tests\Models;
use CoderAtHeart\ObjectModel\ObjectModel;
use CoderAtHeart\ObjectModel\Property;
/**
* @property string label
* @property string number
*/
class Phone extends ObjectModel
{
public static function properties(): array
{
return [
Property::string('label'),
Property::string('number'),
];
}
}
Note the use of the docBlock at the top of the classes - this helps with type hinting in your ide.
Can be done through arrays, json or directly
// Create from an array of data
$person = Person::createFrom(array:[
'name' => 'Coder At Heart',
'age' => 30,
'email' => '[email protected]',
'home' => [
'address_1' => 'Some Street',
'address_2' => 'Some Area',
'city' => 'Some City',
'postcode' => 'AB12 3CD',
],
'business' => [
'address_1' => 'Some Business',
'address_2' => 'Some Location',
'city' => 'Some City',
'postcode' => 'AB99 9DC',
],
'phone_numbers' => [
[
'label' => 'home',
'number' => '01234 67890'
],
[
'label' => 'mobile',
'number' => '09999 123456'
],
],
'birthday' => '1990-01-01'
'important_dates' => [
'2011-01-01 01:01:01',
'2012-02-02 02:02:02',
'2013-03-03 03:03:03',
'2014-04-06 04:04:04',
'2015-05-06 05:05:05',
]
]);
echo $person->name;
// the return value is the underlying object. in this case
// a Carbon Object
echo $person->birthday->format('js F Y');
// Access deep objects
echo $person->home->address_1;
echo $person->phone_numbers[1]->number;
// Save this to json
$json = $person->toJson();
// Create a new person
$bob = Person::createFrom(json: $json)
$bob->name = 'Bob';
// Convert the person to an array
$array = $bob->toArray();
$fred= Person::createFrom(array: $bob)
$fred->name = 'Fred';
$fred->age = 25;
// Just like a normal object:
$isabel = new Person();
$isabel->name = 'Isabel';
$isabel->age = 35;
$isabel->birthday = new Carbon("2002-11-23");
You don't need an Object Model to use an Array Object:
<?php
use CoderAtHeart\ObjectModel\ArrayModel;
$numbers = ArrayModel::create(objectModel: Phone::class);
// Create a new phone number
$homePhone = new Phone();
$homePhone->label = 'home';
$homePhone->number = '01234 567890';
$numbers[] = $homePhone;
echo $numbers[0]->number;
// add to it like a normal array
$numbers[] = [
'label' => 'business',
'number' => '01234 567890',
];
// Access it like an object
echo $numbers[1]->label;
You can use ObjectModels and ArrayModels when creating custom casts in your app - Especially useful when casting json columns.
ObjectsModels:
<?php
namespace App\Casts;
use App\ObjectModels\Person;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class PersonCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return Person::create(json: $value);
}
public function set($model, string $key, $value, array $attributes)
{
return $value->toJson();
}
}
And ArrayModels:
<?php
namespace App\Casts;
use App\ObjectModels\PhonesNumbers;
use CoderAtHeart\ObjectModel\ArrayModel;use CoderAtHeart\ObjectModel\Tests\Models\Person;use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class LogCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return PhonesNumbers::create(json: $value);
// or
return ArrayModel::create(json: $value, objectModel: Phone::class);
}
public function set($model, string $key, $value, array $attributes)
{
return $value->toJson();
}
}
Type | Create With | Underlying Object |
---|---|---|
Another Model | Property::objectModel(); |
ObjectModel |
Bool | Property::bool(); |
php bool |
Date | Property::date(); |
Carbon |
DateTime | Property::dateTime(); |
Carbon |
ENUM | Property::enum(); |
php ENUM |
Property::email(); |
php string | |
Float | Property::float(); |
php string |
Integer | Property::integer(); |
php integer |
Strings | Property::string(); |
php string |
Time | Property::time(); |
Carbon |
an Array | Property::array(); |
php array |
an Array of other models | `Property::objectModelArray(); | arrayModel |
an Array of property | `Property::propertyArray(); | arrayModel |
an Object | Property::object(); |
the Object |
mixed | Property::mixed(); |
any |
Extend the Property
class to add in your own propertires.
<?php
use CoderAtHeart\ObjectModel\Property;
/**
* @property string label
* @property string number
*/
class CustomProperties extends Property
{
/**
* zipCode
*
* @param string $name
*
* @return static
*/
public static function zipCode(string $name): Property
{
return self::property($name)
->addRule('numeric|min:5')
// jsonCallback will be called when teh object is converted to json
->jsonCallback(function ($value) {
return str_replace(' ', '', $value);
})
// This callback is called when the value is set
->setCallback(function ($value) {
return (string) $value;
})
->set(null);
}
}
The two main callbacks are jsonCallback
called when the property is going to be turned into json, and setCallback
when the property is assigned a value.
Here's an object using Custom properties
<?php
use CoderAtHeart\ObjectModel\ObjectModel;
use CoderAtHeart\ObjectModel\CustomProperties;
/**
* @property string address_1
* @property string address_2
* @property string city
* @property string zip
*/
class Address extends ObjectModel
{
public static function properties(): array
{
return [
CustomProperties::string('address_1'),
CustomProperties::string('address_2'),
CustomProperties::string('city'),
CustomProperties::zipCode('zip'),
];
}
}
Object and Arrays can be validated. What you get back is a ObjectValidation Object
<?php
$validation = $person->validate();
// $validation is an ObjectModel
echo $validation->valid;
// or
echo $validation->isValid();
// the name of the object / array
echo $validation->name;
// the errors
dd($validation->errros);
Note: ObjectModels or ArrayModels are not validated automatically.
When you're defining object you can add any valid laravel validation rules
<?php
class Person extends ObjectModel
{
public static function properties(): array
{
return [
Property::string('name')->required(), // just adds 'required' to the rules array
Property::integer('age')->nullable(), // optional
Property::email('email')->addRule('min:20'),
Property::string('first_name')->addRule(new Rule()), // custom rules
];
}
}
You can get rules on an objectModel using $person->getRules()
By default ObjectModels throws an exception if you try and set a property that hasn't been defined.
Get around that by using the IgnoreUndefinedProperties trait
<?php
use \CoderAtHeart\ObjectModel\Traits\IgnoreUndefinedProperties;
class Person extends ObjectModel
{
use IgnoreUndefinedProperties;
public static function properties(): array
{
return [
Property::string('name')->required(), // just adds 'required' to the rules array
Property::integer('age')->nullable(), // optional
Property::email('email')->addRule('min:20'),
Property::string('first_name')->addRule(new Rule()), // custom rules
];
}
}
Let me know... somehow.
ObjectModel is licensed under the MIT License.
v1.1.14