Skip to content

Add schema, control and cast php json as objects and arrays for Laravel projects

License

Notifications You must be signed in to change notification settings

coder-at-heart/object-models

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A handy way to define, use and convert object and arrays to json

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

Requirements

This package requires

  • PHP 8.1 and
  • Laravel 8.0 or higher (including 10).

Installation

Install it:

composer require coder-at-heart/object-models

Defining ObjectModels

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.

Instantiating Objects

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"); 

ArrayModels

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;

Casting eloquent model attributes and json columns

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

Built-in Property Types

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
Email 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

Custom Property Types

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 Validation

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.

Adding Rules

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()

Ignoring undefined properties

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
        ];
    }

}

Got an idea / Suggestion / Improvement?

Let me know... somehow.

Support

License

ObjectModel is licensed under the MIT License.

v1.1.14

About

Add schema, control and cast php json as objects and arrays for Laravel projects

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages