model

by esensi

esensi / model

The base model traits of Esensi

201 Stars 23 Forks Last release: over 2 years ago (0.6.7) MIT License 177 Commits 30 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Esensi Model Traits Package

Build Status Total Downloads Latest Stable Version License

An Esensi package, coded by Emerson Media.

Want to work with us on great Laravel applications? Email us at [email protected]

The

Esensi/Model
package is just one package that makes up Esensi, a platform built on Laravel. This package uses PHP traits to extend Laravel's default Eloquent models and traits. Using traits allows for a high-degree of code reusability and extensibility. While this package provides some reasonable base models, developers are free to mix and match traits in any combination needed, being confident that the code complies to a reliable interface and is properly unit tested. For more details on the inner workings of the traits please consult the generously documented source code.

Have a project in mind? Email us at [email protected], or call 1.877.439.6665.

Quick Start

Notice: This code is specifically designed to be compatible with the Laravel Framework and may not be compatible as a stand-alone dependency or as part of another framework.

Extend the Default Model

The simplest way to demonstrate the traits is to extend the base

Esensi\Model\Model
. For example, if the application requires a simple blog, then the developer could create a

Post
model that automatically handles validation, purging, hashing, encrypting, attribute type juggling and even simplified relationship bindings by simply extending this ready-to-go model:
class Post extends Model {

/**
 * The database table used by the model.
 *
 * @var string
 */
protected $table = 'posts';

}

Pro Tip: The more lean way to use

Esensi\Model
traits is to consume traits on the individual models instead of creating an inheritance dependency. Take a look at the generously commented
Esensi\Model\Model
source code for details on how to use individual traits with and without extending the default model.

Use Soft Deletes Instead

If the application requires that the articles be sent to the trash before permanently deleting them, then the developer can just swap out the

Esensi\Model\Model
with the soft deleting version
Esensi\Model\SoftModel
like so:

class Post extends SoftModel {

}

Pro Tip: While Laravel includes

SoftDeletingTrait
, Esensi expands upon this by also forcing the trait to comply with a
SoftDeletingModelInterface
contract. This ensures a higher level of compatibility and code integrity. You can then do checks like
$model instanceof SoftDeletingModelInterface
to conditionally handle actions.

Table of Contents

Help Write Better Documentation: The documentation is still a work in progress. You can help others learn to reuse code by contributing better documentation as a pull request.

Installation

Add the

esensi/model
package as a dependency to the application. Using Composer, this can be done from the command line:
composer require esensi/model 0.6.*

Or manually it can be added to the

composer.json
file:
{
    "require": {
        "esensi/model": "0.6.*"
    }
}

If manually adding the package, then be sure to run

composer update
to update the dependencies.

Validating Model Trait

This package includes the

ValidatingModelTrait
which implements the
ValidatingModelInterface
on any

Eloquent
model that uses it. The
ValidatingModelTrait
adds methods to
Eloquent
models for:
  • Automatic validation on
    create()
    ,
    update()
    ,
    save()
    ,
    delete()
    , and
    restore()
    methods
  • Integration with Laravel's
    Validation
    services to validate model attributes
  • Integration with Laravel's
    MessageBag
    so that models can return errors when validation fails
  • Option to throw
    ValidationException
    when validation fails
  • Ability to
    forceSave()
    and bypass validation rules entirely
  • Automatic injection (or not) of the model's identifier for
    unique
    validation rules

Like all the traits, it is self-contained and can be used individually. Special credit goes to the very talented Dwight Watson and his Watson/Validating Laravel package which is the basis for this trait. Emerson Media collaborated with him as he created the package. Esensi wraps his traits with consistent naming conventions for the other Esensi model traits. Please review his package in detail to see the inner workings.

Self-Taught Coders Tutorial

This Esensi package has been featured in various places from university classrooms to coding schools to online programming courses. Among one of those online programming courses is Alex Coleman's Self-Taught Coders series From Idea To Launch. Throughout the course, Alex teaches how to design and build a complete Laravel web application. Lesson 24 in the series covers automatic model validation using

Esensi\Model
as a basis for the workflow. According to Alex:

Model validation is the method of establishing rules to ensure when you’re creating, or updating, an object based on a model, that all of its field values are set appropriately. That all required fields are filled, that all date fields are formatted properly, etc.

Auto-Validating On Save

While developers can of course use the

Model
or
SoftModel
classes which already include the
ValidatingModelTrait
, the following code will demonstrate adding auto-validation to any

Eloquent
based model.
class Post extends Eloquent implements ValidatingModelInterface {

use ValidatingModelTrait;

/**
 * These are the default rules that the model will validate against.
 * Developers will probably want to specify generic validation rules
 * that would apply in any save operation vs. form or route
 * specific validation rules. For simple models, these rules can
 * apply to all save operations.
 *
 * @var array
 */
protected $rules = [
   'title'     => [ 'max:64' ],
   'slug'      => [ 'max:16', 'alpha_dash', 'unique' ],
   'published' => [ 'boolean' ],
   // ... more attribute rules
];

/**
 * These are the rulesets that the model will validate against
 * during specific save operations. Rulesets should be keyed
 * by either the in progress event name of the save operation
 * or a custom unique key for custom validation.
 *
 * The following rulesets are automatically applied during
 * corresponding save operations:
 *
 *     "creating" after "saving" but before save() is called (on new models)
 *     "updating" after "saving" but before save() is called (on existing models)
 *     "saving" before save() is called (and only if no "creating" or "updating")
 *     "deleting" when calling delete() method
 *     "restoring" when calling restore() method (on a soft-deleting model)
 *
 * @var array
 */
protected $rulesets = [

    'creating' => [
        'title'     => [ 'required', 'max:64' ],
        'slug'      => [ 'required', 'alpha_dash', 'max:16', 'unique' ],
        'published' => [ 'boolean' ],
        // ... more attribute rules to validate against when creating
    ],

    'updating' => [
        'title'     => [ 'required', 'max:64' ],
        'slug'      => [ 'required', 'alpha_dash', 'max:16', 'unique' ],
        'published' => [ 'boolean' ],
        // ... more attribute rules to validate against when updating
    ],
];

}

Then from the controller or repository the developer can interact with the

Post
model's attributes, call the
save()
method and let the
Post
model handle validation automatically. For demonstrative purposes the following code shows this pattern from a simple route closure:
Route::post( 'posts', function()
{
    // Hydrate the model from the Input
    $attributes = Input::only( 'title', 'slug', 'published' );
    $post = new Post( $attributes );

// Attempt to save, will return false on invalid model.
// Because this is a new model, the "creating" ruleset will
// be used to validate against. If it does not exist then the
// "saving" ruleset will be attempted. If that does not exist, then
// finally it will default to the Post::$rules.
if ( ! $post->save() )
{
    // Redirect back to the form with the message bag of errors
    return Redirect::to( 'posts' )
        ->withErrors( $post->getErrors() )
        ->withInput();
}

// Redirect to the new post
return Redirect::to( 'posts/' . $post->id );

});

Calling the

save()
method on the newly created
Post
model would instead use the "updating" ruleset from
Post::$ruleset
while saving. If that ruleset did not exist then it would default to using the
Post::$rules
.

Pro Tip: While using this pattern is perfectly fine, try not to actually validate your form requests using such rulesets. Instead use Laravel 5.1's

FormRequest
injection to validate your forms. The
ValidatingModelTrait
is for validating your model's data integrity, not your entry form validation.

Purging Model Trait

This package includes the

PurgingModelTrait
which implements the
PurgingModelInterface
on any

Eloquent
model that uses it. The
PurgingModelTrait
adds methods to
Eloquent
models for automatically purging attributes from the model just before write operations to the database. The trait automatically purges:
  • attributes in the
    $purgeable
    property
  • attributes prefixed with an underscore (i.e.:
    _private
    )
  • attributes ending in
    _confirmation
    (i.e.:
    password_confirmation
    )

Like all the traits, it is self-contained and can be used individually.

Pro Tip: This trait uses the

PurgingModelObserver
to listen for the
eloquent.creating
and
eloquent.updating
events before automatically purging the purgeable attributes. The order in which the traits are used in the
Model
determines the event priority: if using the
ValidatingModelTrait
be sure to use it first so that the purging event listner is fired after the validating event listener has fired.

Auto-Purging on Save

While developers can of course use the

Model
or
SoftModel
classes which already include the
PurgingModelTrait
, the following code will demonstrate using automatic purging on any

Eloquent
based model.
class Post extends Eloquent implements PurgingModelInterface {

use PurgingModelTrait;

/**
 * These are the attributes to purge before saving.
 *
 * Remember, anything prefixed with "_" or ending
 * in "_confirmation" will automatically be purged
 * and does not need to be listed here.
 *
 * @var array
 */
protected $purgeable = [
    'analytics_id',
    '_private_attribute',
    'password_confirmation',
];

}

Pro Tip: From an efficiency stand point, it is theoretically better to assign all purgeable attributes in the

$purgeable
property including underscore prefixed and
_confirmation
suffixed attributes since the
$purgeable
property is checked first and does not require string parsing and comparisons.

The developer can now pass form input to the

Post
model from a controller or repository and the trait will automatically purge the non-attributes before saving. This gets around those pesky "Unknown column" MySQL errors when the set value is mutated to an internal attribute or when you conditionally want to ignore any fill values. For demonstrative purposes the following code shows this in practice from a simple route closure:
Route::post( 'posts', function( $id )
{
    // Hydrate the model from the Input
    $input = Input::all();
    $post = new Post($input);

// At this point $post->analytics_id might exist.
// If we tried to save it, MySQL would throw an error.

// Save the Post
$post->save();

// At this point $post->analytics_id is for sure purged.
// It was excluded becaused it existed in Post::$purgeable.

});

Manually Purging Model Attributes

It is also possible to manually purge attributes. The

PurgingModelTrait
includes several helper functions to make manual manipulation of the
$purgeable
property easier.
// Hydrate the model from the Input
$post = Post::find($id);
$post->fill( Input::all() );

// Manually purge attributes prior to save() $post->purgeAttributes();

// Manually get the attributes $post->getHashable(); // ['foo']

// Manually set the purgeable attributes $post->setPurgeable( ['foo', 'bar'] ); // ['foo', 'bar']

// Manually add an attribute to the purgeable attributes $post->addPurgeable( 'baz' ); // ['foo', 'bar', 'baz'] $post->mergePurgeable( ['zip'] ); // ['foo', 'bar', 'baz', 'zip'] $post->removePurgeable( 'foo' ); // ['bar', 'baz', 'zip']

// Check if an attribute is in the Post::$purgeable property if ( $post->isPurgeable( 'foo' ) ) { // ... foo is not purgeable so this would not get executed }

// Do not run purging for this save only. // This is useful when purging is enabled // but needs to be temporarily bypassed. $post->saveWithoutPurging();

// Disable purging $post->setPurging(false); // a value of true would enable it

// Run purging for this save only. // This is useful when purging is disabled // but needs to be temporarily ran while saving. $post->saveWithPurging();

Hashing Model Trait

This package includes the

HashingModelTrait
which implements the
HashingModelInterface
on any

Eloquent
model that uses it. The
HashingModelTrait
adds methods to
Eloquent
models for automatically hashing attributes on the model just before write operations to the database. The trait includes the ability to:

  • automatically hash attributes in the
    $hashable
    property
  • manually hash a value using the
    hash()
    method
  • compare a plain text value with a hash using the
    checkHash()
    method
  • check if a value is hashed using the
    isHashed()
    method
  • swap out the
    HasherInterface
    used using the
    setHasher()
    method

Like all the traits, it is self-contained and can be used individually.

Pro Tip: This trait uses the

HashingModelObserver
to listen for the
eloquent.creating
and
eloquent.updating
events before automatically hashing the hashable attributes. The order in which the traits are used in the
Model
determines the event priority: if using the
ValidatingModelTrait
be sure to use it first so that the hashing event listner is fired after the validating event listener has fired.

Auto-Hashing on Save

While developers can of course use the

Model
or
SoftModel
classes which already include the
HashingModelTrait
, the following code will demonstrate using automatic hashing on any

Eloquent
based model. For this example, the implementation of automatic hashing will be applied to a
User
model which requires the password to be hashed on save:
class User extends Eloquent implements HashingModelInterface {

use HashingModelTrait;

/**
 * These are the attributes to hash before saving.
 *
 * @var array
 */
protected $hashable = [ 'password' ];

}

Pro Tip: The

HashingModelTrait
is a great combination for the
PurgingModelTrait
. Often hashable attributes need to be confirmed and using the
PurgingModelTrait
, the model can be automatically purged of the annoying
_confirmation
attributes before writing to the database. While the
use
order of these two traits is not important relative to each other, it is important to
use
them after
ValidatingModelTrait
if that trait is used as well. Otherwise, the model will purge or hash the attributes before validating.

The developer can now pass form input to the

User
model from a controller or repository and the trait will automatically hash the
password
before saving. For demonstrative purposes the following code shows this in practice from a simple route closure:
Route::post( 'account', function()
{
    // Hydrate the model from the Input
    $user = Auth::user();
    $user->password = Input::get('password');

// At this point $user->password is still plain text.
// This allows for the value to be checked by validation.

// Save the User
$user->save();

// At this point $user->password is for sure hashed.
// It was hashed becaused it existed in User::$hashable.

});

Manually Hashing Model Attributes

It is also possible to manually hash attributes. The

HashingModelTrait
includes several helper functions to make manual manipulation of the
$hashable
property easier.
// Hydrate the model from the Input
$post = User::find($id);
$post->password = Input::get('password');

// Manually hash attributes prior to save() $post->hashAttributes();

// Manually get the attributes $post->getHashable(); // ['foo']

// Manually set the hashable attributes $post->setHashable( ['foo', 'bar'] ); // ['foo', 'bar']

// Manually add an attribute to the hashable attributes $post->addHashable( 'baz' ); // ['foo', 'bar', 'baz'] $post->mergeHashable( ['zip'] ); // ['foo', 'bar', 'baz', 'zip'] $post->removeHashable( 'foo' ); // ['bar', 'baz', 'zip']

// Check if an attribute is in the User::$hashable property if ( $post->isHashable( 'foo' ) ) { // ... foo is not hashable so this would not get executed }

// Check if an attribute is already hashed if ( $post->isHashed( 'foo' ) ) { // ... if foo were hashed this would get executed }

// Check if the password when hashed matches the stored password. // This is just a unified shorthand to Crypt::checkHash(). if ( $post->checkHash( 'password123', $post->password ) ) { // ... if the password matches you could authenticate the user }

// Swap out the HasherInterface used $post->setHasher( new MyHasher() );

// Do not run hashing for this save only. // This is useful when hashing is enabled // but needs to be temporarily bypassed. $post->saveWithoutHashing();

// Disable hashing $post->setHashing(false); // a value of true would enable it

// Run hashing for this save only. // This is useful when hashing is disabled // but needs to be temporarily ran while saving. $post->saveWithHashing();

Encrypting Model Trait

This package includes the

EncryptingModelTrait
which implements the
EncryptingModelInterface
on any

Eloquent
model that uses it. The
EncryptingModelTrait
adds methods to
Eloquent
models for automatically encrypting attributes on the model whenever they are set and for automatically decrypting attributes on the model whenever they are got. The trait includes the ability to:

  • automatically encrypt attributes in the
    $encryptable
    property when setting them
  • automatically decrypt attributes in the
    $encryptable
    property when getting them
  • manually encrypt/decrypt a value using the
    encrypt()
    and
    decrypt()
    methods
  • check if a value is encrypted using the
    isEncrypted()
    method
  • swap out the encrypter class used using the
    setEncrypter()
    method

Like all the traits, it is self-contained and can be used individually. Be aware, however, that using this trait does overload the magic

__get()
and
__set()
methods of the model (see Esensi\Model\Model source code for how to deal with overloading conflicts).

Manually Encrypting Model Attributes

It is also possible to manually encrypt attributes. The

EncryptingModelTrait
includes several helper functions to make manual manipulation of the
$encryptable
property easier.
// Hydrate the model from the Input
$post = Model::find($id);
$post->secret = Input::get('secret'); // automatically encrypted

// Manually encrypt attributes prior to save() $post->encryptAttributes();

// Manually encrypt and decrypte a value $encrypted = $post->encrypt( 'plain text' ); $decrypted = $post->decrypt( $encrypted ); // plain text

// Manually get the attributes $post->getEncryptable(); // ['foo']

// Manually set the encryptable attributes $post->setEncryptable( ['foo', 'bar'] ); // ['foo', 'bar']

// Manually add an attribute to the encryptable attributes $post->addEncryptable( 'baz' ); // ['foo', 'bar', 'baz'] $post->mergeEncryptable( ['zip'] ); // ['foo', 'bar', 'baz', 'zip'] $post->removeEncryptable( 'foo' ); // ['bar', 'baz', 'zip']

// Check if an attribute is in the Model::$encryptable property if ( $post->isEncryptable( 'foo' ) ) { // ... foo is not encryptable so this would not get executed }

// Check if an attribute is already encrypted. // You could also check $post->isDecrypted( 'foo' ). if ( $post->isEncrypted( 'foo' ) ) { // ... if foo were encrypted this would get executed }

// Swap out the encrypter class used $post->setEncrypter( new MyEncrypter() );

// Disable encrypting $post->setEncrypting(false); // a value of true would enable it

Juggling Model Trait

This package includes the

JugglingModelTrait
which implements the
JugglingModelInterface
on any

Eloquent
model that uses it. The
JugglingModelTrait
adds methods to
Eloquent
models for automatically type casting (juggling) attributes on the model whenever they are got or set. The trait includes the ability to:
  • automatically cast attributes to a type when getting them
  • automatically cast attributes to a type when setting them
  • manually casting a value using the
    juggle()
    method
  • manually casting to pre-defined types including:
    • null
      =>
      juggleNull()
      (returns null on empty)
    • string
      =>
      juggleString()
    • boolean
      (
      bool
      ) =>
      juggleBoolean()
    • integer
      (
      integer
      ) =>
      juggleInteger()
    • float
      (
      double
      ) =>
      juggleFloat()
    • array
      =>
      juggleArray()
    • date
      =>
      juggleDate()
      (returns Carbon date)
    • datetime
      (
      date_time
      ) =>
      juggleDateTime()
      (returns as 0000-00-00 00:00:00)
    • timestamp
      =>
      juggleTimestamp()
      (returns Unix timestamp)
  • create custom types to cast to with magic model methods like:
    • Example:
      fooBar
      =>
      juggleFooBar()

Like all the traits, it is self-contained and can be used individually. Be aware, however, that using this trait does overload the magic

__get()
and
__set()
methods of the model (see Esensi\Model\Model source code for how to deal with overloading conflicts). Special credit goes to the brilliant Dayle Rees, author of Code Bright book, who inspired this trait with his pull request to Laravel which eventually arrived in Laravel 5.0 as Attribute Casting which supports basic type casting.

Auto-Juggling on Access

Pro Tip: PHP extensions like

php-mysqlnd
should be used when available to handle casting from and to persistent storage, this trait serves a dual purpose of type casting and simplified attribute mutation (juggling) especially when a native extension is not available.

While developers can of course use the

Model
or
SoftModel
classes which already include the
JugglingModelTrait
, the following code will demonstrate using automatic type juggling on any

Eloquent
based model. For this example, the implementation of automatic type juggling will be applied to a
Post
model which requires certain attributes to be type casted when attributes are accessed:
class Post extends Eloquent implements JugglingModelInterface {

use JugglingModelTrait;

/**
 * Attributes to cast to a different type.
 *
 * @var array
 */
protected $jugglable = [

    // Juggle the published_at attribute to a date
    'published_at' => 'date',

    // Cast the terms attribute to a boolean
    'terms' => 'boolean',

    // Juggle the foo attribute to a custom bar type
    'foo' => 'bar',
];

/**
 * Example of a custom juggle "bar" type.
 *
 * @param  mixed $value
 * @return \Bar
 */
protected function juggleBar( $value )
{
    return new Bar($value);
}

}

The developer can now pass form input to the

Post
model from a controller or repository and the trait will automatically type cast/juggle the attributes when setting. The same holds true for when the attributes are loaded from persistent storage as the model is constructed: the attributes are juggled to their types. Even for persistent storage that does not comply, the jugglable attributes are automatically type casted when retrieved from the model. For demonstrative purposes the following code shows this in practice from a simple route closure:
Route::post( 'post/{id}/publish', function( $id )
{
    // Hydrate the model from the Input
    $post = Post::find($id);

// The published_at attribute will be converted to a Carbon date
// object. You could then use $post->published_at->format('Y-m-d').
$post->published_at = Input::get('published_at');

// Convert those pesky checkboxes to proper booleans.
$post->terms = Input::get('terms', false);

// The foo attribute will be casted as the custom "bar" type using
// juggleBar() so it's value would now be a Bar object.
$post->foo = Input::get('foo');

// Save the Post or do something else
$post->save();

});

Pro Tip: Some great uses for

JugglingModelTrait
would be custom "types" that map to commonly mutators jugglers for
phone
,
url
,
json
, types etc. Normally developers would have to map the attributes to attribute mutators and accessors which are hard-coded to the attribute name. Using the
$jugglable
property these attributes can be mapped to custom juggle methods easily in a reusable way. Custom services could be used as part of the juggle method too. This would make converting currency from one type to a normalized type or to convert from floating values (1.99) to integers (199) when persisted.

Manually Juggling Model Attributes

It is also possible to manually juggle attributes. The

JugglingModelTrait
includes several helper functions to make manual manipulation of the
$jugglable
property easier.
// Hydrate the model from the Input
$post = Model::find($id);
$post->foo = Input::get('foo'); // automatically juggled

// Manually juggle attributes after setting $post->juggleAttributes();

// Manually juggle a value to a type $boolean = $post->juggle( 'true', 'boolean' ); // bool(true) $boolean = $post->juggleBoolean( '0' ); // bool(false) $array = $post->juggleArray( 'foo' ); // array(0 => foo) $date = $post->juggleDate( '2014-07-10' ); // object(\Carbon\Carbon) $dateTime = $post->juggleDateTime( Carbon::now() ); // string(2014-07-10 11:17:00) $timestamp = $post->juggleTimestamp( '07/10/2014 11:17pm' ); // integer(1405034225)

// Manually get the attributes $post->getJugglable(); // ['foo' => 'string']

// Manually set the jugglable attributes $post->setJugglable( ['bar' => 'boolean'] ); // ['bar' => 'boolean']

// Manually add an attribute to the jugglable attributes $post->addJugglable( 'baz', 'integer' ); // ['bar' => 'boolean', 'baz' => 'integer'] $post->mergeJugglable( ['zip' => 'array'] ); // ['bar' => 'boolean', 'baz' => 'integer', 'zip' => 'array'] $post->removeJugglable( 'bar' ); // ['baz' => 'integer', 'zip' => 'array']

// Check if an attribute is in the Model::$jugglable property if ( $post->isJugglable( 'foo' ) ) { // ... foo is not jugglable so this would not get executed }

// Check if a type is castable // For this example juggleBar() is not a method. if ( $post->isJuggleType( 'bar' ) ) { // ... this code wouldn't get executed because bar is not a cast type }

// Throws an exception on invalid cast type // It's used internally by setJugglable() to enforce valid cast types $post->checkJuggleType( 'bar' );

// Disable juggling $post->setJuggling(false); // a value of true would enable it

Soft Deleting Model Trait

This package includes the

SoftDeletingModelTrait
which implements the
SoftDeletingModelInterface
on any

Eloquent
model that uses it. The
SoftDeletingModelTrait
wraps the default
Eloquent
model's
SoftDeletingTrait
for a unified naming convention and stronger interface hinting. The trait also includes the ability to set additional dates in the
$dates
property without having to remember to add
deleted_at
.

Like all the traits, it is self-contained and can be used individually. As a convenience, the

Esensi\Model\SoftModel
extends the
Esensi\Model\Model
and implements the trait already. The developer can just extend the

SoftModel
and not have to refer to the Laravel soft deleting documentation again.

Pro Tip: Just because a model uses the

SoftDeletingModelTrait
does not mean that the database has the
deleted_at
column in its table. Be sure to add
$table->softDeletes();
to a table migration.

Relating Model Trait

This package includes the

RelatingModelTrait
which implements the
RelatingModelInterface
on any

Eloquent
model that uses it. The
RelatingModelTrait
adds methods to
Eloquent
models for automatically resolving related models:
  • from simplified configs using the
    $relationships
    property
  • add pivot attributes from simplified configs using the
    $relationshipPivots
    property
  • as magic method calls such as
    Post::find($id)->comments()->all()
  • as magic attribute calls such as
    Post::find($id)->author

Pro Tip: As an added bonus, this trait includes a special Eloquent

without()
scope which accepts relationships to remove from the eager loaded list, exactly opposite of the built in Eloquent support for
with()
. This is particularly useful for models that set the
$with
property but occassionally need to remove the eager loading to improve performance on larger queries. This does not impact lazy/manual loading using the dynamic or
load()
methods.

Like all the traits, it is self-contained and can be used individually. Be aware, however, that using this trait does overload the magic

__call()
and
__get()
methods of the model (see Esensi\Model\Model source code for how to deal with overloading conflicts). Special credit goes to Phillip Brown and his Philipbrown/Magniloquent Laravel package which inspired this trait.

Using Simplified Relationships

While developers can of course use the

Model
or
SoftModel
classes which already include the
RelatingModelTrait
, the following code will demonstrate adding simplified relationship bindings to any

Eloquent
based model.
class Post extends Eloquent implements RelatingModelInterface {

use RelatingModelTrait;

/**
 * These are the relationships that the model should set up.
 * Using PHP and Laravel's magic, these relationship keys
 * resolve to the actual models automatically.
 *
 * @example relationship bindings:
 *
 *     [ 'hasOne', 'related', 'foreignKey', 'localKey' ]
 *     [ 'hasMany', 'related', 'foreignKey', 'localKey' ]
 *     [ 'hasManyThrough', 'related', 'through', 'firstKey', 'secondKey' ]
 *     [ 'belongsTo', 'related', 'foreignKey', 'otherKey', 'relation' ]
 *     [ 'belongsToMany', 'related', 'table', 'foreignKey', 'otherKey', 'relation' ]
 *     [ 'morphOne', 'related', 'name', 'type', 'id', 'localKey' ]
 *     [ 'morphMany', 'related', 'name', 'type', 'id', 'localKey' ]
 *     [ 'morphTo', 'name', 'type', 'id' ]
 *     [ 'morphToMany', 'related', 'name', 'table', 'foreignKey', 'otherKey', 'inverse' ]
 *     [ 'morphedByMany', 'related', 'name', 'table', 'foreignKey', 'otherKey' ]
 *
 * @var array
 */
protected $relationships = [

    // Bind Comment model as a hasMany relationship.
    // Use $post->comments to query the relationship.
    'comments' => [ 'hasMany', 'Comment' ],

    // Bind User model as a belongsTo relationship.
    // Use $post->author to get the User model.
    'author' => [ 'belongsTo', 'User' ],

    // Bind User model as a belongsTo relationship.
    // Use $post->author to get the User model.
    'tags' => [ 'belongsToMany', 'Tag']
];

/**
 * These are the additional pivot attributes that the model
 * will setup on the relationships that support pivot tables.
 *
 * @var array
 */
protected $relationshipPivots = [

    // Bind pivot attributes to Tag model when querying the relationship.
    // This is equivalent to $post->tags()->withTimestamps()->withPivot('foo').
    'tags' => [ 'timestamps', 'foo' ]
];

}

The developer can now use the

Post
model's relationships from a controller or repository and the trait will automatically resolve the relationship bindings. For demonstrative purposes the following code shows this pattern from a simple route closure:
Route::get( 'posts/{id}/comments', function( $id )
{
    // Retrieve the post by ID
    $post = Post::find( $id );

// Query the post for all the related comments.
// The trait will resolve the "comments" from
// the Post::$relationships bindings.
$comments = $post->comments()->all();

// It is also possible to shorten this using the
// magic attributes instead. It is equivalent to
// the above call.
$comments = $post->comments;

// Access the pivot table columns off a
// many-to-many relationship model.
$tag = $post->tags()->first();
$carbon = $tag->pivot->created_at; // Carbon Date
$bar = $tag->pivot->foo;

});

Unit Testing

The Esensi platform includes other great packages just like this Esensi/Model package. This package is currently tagged as

0.5.x
because the other platform packages are not ready for public release. While the others may still be under development, this package already includes features that would be mature enough for a
1.x
release including unit testing and extensive testing in real-world applications.

Running the Unit Tests

This package uses PHPUnit to automate the code testing process. It is included as one of the development dependencies in the

composer.json
file:
{
    "require-dev": {
        "phpunit/phpunit": "4.1.*",
        "mockery/mockery": "0.9.*"
    }
}

The test suite can be ran from the command line using the

phpunit
test runner:
phpunit ./tests

Important: There is currently a bug in Laravel (see issue #1181) that prevents model events from firing more than once in a test suite. This means that the first test that uses model tests will pass but any subseqeuent tests will fail. There are a couple of temporary solutions listed in that thread which you can use to make your tests pass in the meantime: namely

Model::flushEventListeners()
and
Model::boot()
after each test runs.

Pro Tip: Please help the open-source community by including good code test coverage with your pull requests. The Esensi development team will review pull requests with unit tests and passing tests as a priority. Significant code changes that do not include unit tests will not be merged.

Contributing

Emerson Media is proud to work with some of the most talented developers in the PHP community. The developer team welcomes requests, suggestions, issues, and of course pull requests. When submitting issues please be as detailed as possible and provide code examples where possible. When submitting pull requests please follow the same code formatting and style guides that the Esensi code base uses. Please help the open-source community by including good code test coverage with your pull requests. All pull requests must be submitted to the version branch to which the code changes apply.

Note: The Esensi team does its best to address all issues on Wednesdays. Pull requests are reviewed in priority followed by urgent bug fixes. Each week the package dependencies are re-evaluated and updates are made for new tag releases.

Licensing

Copyright (c) 2015 Emerson Media, LP

This package is released under the MIT license. Please see the LICENSE.txt file distributed with every copy of the code for commercial licensing terms.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.