PHPackages                             mortimer/poignant - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. mortimer/poignant

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

mortimer/poignant
=================

Eloquent and Ardent on steroids, adds in-model validations, user stamping, declarative relations, cascaded operations on relations (save, delete). To make RoR devs feel a bit more at home.

12901[1 issues](https://github.com/phurni/mortimer-poignant/issues)PHP

Since Mar 17Pushed 11y ago3 watchersCompare

[ Source](https://github.com/phurni/mortimer-poignant)[ Packagist](https://packagist.org/packages/mortimer/poignant)[ RSS](/packages/mortimer-poignant/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Poignant
========

[](#poignant)

Eloquent on steroids, implements RoR like ActiveRecord features as traits, including in-model validations, user stamping, declarative relations, cascaded operations on relations (save, delete).

The DeclarativeRelations is extracted from [Ardent](https://github.com/laravelbook/ardent) by Max Ehsan (see LICENSE\_Ardent).

Copyright (C) 2014-2015 Pascal Hurni &lt;&gt;

Licensed under the [MIT License](http://opensource.org/licenses/MIT).

Installation
------------

[](#installation)

Add `mortimer/poignant` as a requirement to `composer.json`:

```
{
    "require": {
        "mortimer/poignant": "dev-master"
    }
}
```

Update your packages with `composer update` or install with `composer install`.

The master branch works for both Laravel 4.2 and 5.0

Getting Started
---------------

[](#getting-started)

`Poignant` is a collection of PHP traits for Eloquent models. You are free to use any or many of those traits by simply adding the `use` statement in your model, like this:

```
use Mortimer\Poignant\UserStamping;

class MyModel extends Eloquent {
  use UserStamping;
}
```

You may also import all of them by inheriting from the `Model` class:

```
use Mortimer\Poignant\Model;

class MyModel extends Model {
}
```

Documentation
-------------

[](#documentation)

Here is the list of traits with their behaviours, you'll find the detailed documentation in the next chapters.

- `UserStamping`. Provides automatic filling of `created_by_id`, `updated_by_id` and `deleted_by_id` attributes.
- `DeclarativeRelations`. Eases the declaration of relationships with a property array.
- `CascadedRelations`. Handles cascaded operations for relations. Notably save() and delete().
- `ModelValidation`. Adds in-model validation and model scoped I18n for attributes and messages.
- `ExistingAttributesPersistence`. Automatically purge any attributes that are not DB columns before saving.

### UserStamping

[](#userstamping)

Provides automatic filling of `created_by_id`, `updated_by_id` and `deleted_by_id` attributes. These are filled only if the columns exists on the table, so no need to worry about using or not the trait, simply use and forget it.

This trait stamps the user by using its `id` not a name string. So you may add relations on your models to associate them correctly.

Every column name may be customized by defining them in your model:

```
use Mortimer\Poignant\UserStamping;

class MyModel extends Eloquent {
  use UserStamping;

  protected static $CREATED_BY = 'FK_created_by';
}
```

You may also override the value stored in those columns:

```
use Mortimer\Poignant\UserStamping;

class MyModel extends Eloquent {
  use UserStamping;

  public function getUserStampValue()
  {
      // This is the default value, return what you desire here to override the default behaviour
      return \Auth::user()->getKey();
  }
}
```

### DeclarativeRelations

[](#declarativerelations)

This one is extracted from Ardent which itself picked the idea from the Yii framework.

Can be used to ease declaration of relationships in Eloquent models. Follows closely the behavior of the relation methods used by Eloquent, but packing them into an indexed array with relation constants make the code less cluttered.

It should be declared with camel-cased keys as the relation name, and value being a mixed array with the relation constant being the first (0) value, the second (1) being the classname and the next ones (optionals) having named keys indicating the other arguments of the original methods: 'foreignKey' (belongsTo, hasOne, belongsToMany and hasMany); 'table' and 'otherKey' (belongsToMany only); 'name', 'type' and 'id' (specific for morphTo, morphOne and morphMany). Exceptionally, the relation type MORPH\_TO does not include a classname, following the method declaration of `\Illuminate\Database\Eloquent\Model::morphTo`.

Example:

```
use Mortimer\Poignant\DeclarativeRelations;
use Mortimer\Poignant\DeclarativeRelationsTypes as DRT;

class Order extends Eloquent {
    use DeclarativeRelations;

    protected static $relationsData = [
        'items'    => [DRT::HAS_MANY, 'Item'],
        'owner'    => [DRT::HAS_ONE, 'User', 'foreignKey' => 'user_id'],
        'pictures' => [DRT::MORPH_MANY, 'Picture', 'name' => 'imageable']
    ];
}
```

Or by extending the base model (no more needs of DRT):

```
use Mortimer\Poignant\Model;

class Order extends Model {
    use DeclarativeRelations;

    protected static $relationsData = [
        'items'    => [self::HAS_MANY, 'Item'],
        'owner'    => [self::HAS_ONE, 'User', 'foreignKey' => 'user_id'],
        'pictures' => [self::MORPH_MANY, 'Picture', 'name' => 'imageable']
    ];
}
```

You may also add specific relationships in sub-classes, their parent relationships will be gathered automatically, no need for special care.

For fellow developers that want to pick the relations declaration from anywhere else than the `$relationsData` property, you simply have to override these two methods to accomodate for this:

- `protected static function getDeclaredRelationships()`
- `protected static function getInheritedDeclaredRelationships()`

### CascadedRelations

[](#cascadedrelations)

Handles cascaded operations for relations. Notably save() and delete().

### ModelValidaton

[](#modelvalidaton)

Import validation into the model. After all Eloquent is an ActiveRecord, so validating the record before saving is coherent.

Now to the point. ModelValidation uses the `ValidatingTrait` by [Dwight Watson](https://github.com/dwightwatson/validating)but extends it so that custom validation rules may be defined **directly into the model**. Simply define the `validate*` and `replace*` methods directly in the model instead of the Validator class. You may also override the default `validate*`methods for your own use (always scoped to the model).

Additional validations rules may also be declared in subclasses, the ModelValidation trait will gather rules in the whole class hierarchy.

Example:

```
use Mortimer\Poignant\ModelValidation;

class Booking extends Eloquent {
    use ModelValidation;

    protected static $rules = [
      'title' => 'required',
      'starts_at' => 'required|date',
      'ends_at' => 'required|date',
      'items_ids' => 'required|required_with_all:starts_at,ends_at|available|array|min:1',
    ];

    protected $availableErrors = [];  // communication point between validateAvailable() and replaceAvailable()

    public function validateAvailable($attribute, $value, $parameters, $validator)
    {
      // By default, we are valid
      $available = true;
      $this->availableErrors = [];

      // Get the source of items either overridden ones or existing ones
      $items_ids = $this->items_ids ? $this->items_ids : $this->items()->modelKeys();

      // Fetch the possible conflicting bookings
      $conflictingBookings = static::whereHas('items', function($query) use ($items_ids) { $query->whereIn('items.id', $items_ids);})->between($this->starts_at, $this->ends_at)->with('created_by')->get();

      // Any conflict?
      $available = $conflictingBookings->isEmpty();

      // Inform the user we have conflicts
      if (!$available) {
        foreach ($conflictingBookings as $conflictingBooking) {
          $this->availableErrors[] = \Lang::get('bookings/validation.items_ids.unavailable_item', [
            'title'    => $conflictingBooking->title,
            'resource' => $conflictingBooking->items->implode('name', ', '),
            'from'     => $conflictingBooking->starts_at,
            'to'       => $conflictingBooking->ends_at,
            'user'     => $conflictingBooking->created_by->fullname]
          );
        }
      }

      return $available;
    }

    public function replaceAvailable($message, $attribute, $rule, $parameters)
    {
      return str_replace(':available', join("\n", $this->availableErrors), $message);
    }
}
```

This example shows that having your custom validate method leverages the fact that we are in the model because we use its `items` relation to fetch the possible conflicting bookings. Something that may be hard to achieve otherwise.

Attributes and messages may be customized and localized automagically. Simply define locale files for the custom items you want. Here is an example for the `Booking` model defined above:

`app/lang/en/bookings/attributes.php`

```
