mortimer/poignant

Eloquent和Ardent的增强版,增加了模型验证、用户标记、声明式关系、关系级联操作(保存、删除)。让RoR开发者感觉更加舒适。

dev-master 2015-03-17 07:13 UTC

This package is auto-updated.

Last update: 2024-09-09 23:42:38 UTC


README

Eloquent的增强版,通过特性实现了类似RoR的ActiveRecord功能,包括模型验证、用户标记、声明式关系、关系级联操作(保存、删除)。

DeclarativeRelations是从Max Ehsan的Ardent中提取的(见LICENSE_Ardent)。

版权 (C) 2014-2015 Pascal Hurni <https://github.com/phurni>

在MIT许可证下发布。

安装

mortimer/poignant添加到composer.json的要求中

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

使用composer update更新您的包或使用composer install安装。

主分支适用于Laravel 4.2和5.0

入门

Poignant是一组用于Eloquent模型的PHP特性。您可以通过在模型中添加use语句来简单地使用这些特性中的任何一个或多个,如下所示

use Mortimer\Poignant\UserStamping;

class MyModel extends Eloquent {
  use UserStamping;
}

您也可以通过从Model类继承来导入所有这些特性

use Mortimer\Poignant\Model;

class MyModel extends Model {
}

文档

以下是特性及其行为的列表,您将在下一章中找到详细的文档。

  • UserStamping。提供自动填充created_by_idupdated_by_iddeleted_by_id属性。

  • DeclarativeRelations。简化了通过属性数组声明关系。

  • CascadedRelations。处理关系的级联操作。特别是save()和delete()。

  • ModelValidation。添加模型验证和模型作用域I18n属性和消息。

  • ExistingAttributesPersistence。在保存之前自动清除任何不是数据库列的属性。

UserStamping

提供自动填充created_by_idupdated_by_iddeleted_by_id属性。这些属性只有在表上有列时才会填充,因此无需担心是否使用特性,只需使用并忘记即可。

此特性使用其id而不是名称字符串来标记用户。因此,您可以在模型上添加关系以正确关联它们。

每个列名都可以通过在模型中定义它们来自定义

use Mortimer\Poignant\UserStamping;

class MyModel extends Eloquent {
  use UserStamping;
  
  protected static $CREATED_BY = 'FK_created_by';
}

您也可以覆盖这些列中存储的值

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

这个是从Ardent提取的,Ardent本身又从Yii框架中获得了灵感。

可以用于简化Eloquent模型中关系的声明。它紧密遵循Eloquent使用的关联方法的行为,但通过将它们打包到一个索引数组中,使代码更简洁。

它应该使用驼峰命名法作为关系名称声明,值是一个混合数组,其中关系常量是第一个(0)值,第二个(1)是类名,接下来的是可选的,具有命名键,表示原始方法的其他参数:‘foreignKey’(belongsTo、hasOne、belongsToMany和hasMany);‘table’和‘otherKey’(belongsToMany仅限);‘name’、‘type’和‘id’(特定于morphTo、morphOne和morphMany)。例外的是,MORPH_TO关系类型不包括类名,遵循\Illuminate\Database\Eloquent\Model::morphTo的方法声明。

示例

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

或通过扩展基本模型(不再需要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']
    ];
}

您还可以在子类中添加特定的关系,其父类关系将自动收集,无需特别关注。

对于想要从除了 $relationsData 属性以外的任何地方获取关系声明的开发者,您只需重写这两个方法以适应这种情况

  • 受保护的静态函数 getDeclaredRelationships()
  • 受保护的静态函数 getInheritedDeclaredRelationships()

CascadedRelations

处理关系的级联操作。特别是 save() 和 delete()。

ModelValidation

将验证导入模型。毕竟 Eloquent 是一个 ActiveRecord,因此在保存记录之前进行验证是合理的。

现在进入正题。ModelValidation 使用 Dwight Watson 的 ValidatingTrait,但扩展了它,以便可以在 模型中直接 定义自定义验证规则。只需在模型中直接定义 validate*replace* 方法,而不是在 Validator 类中。您还可以重写默认的 validate* 方法以供自己使用(始终限于模型)。

子类中也可以声明额外的验证规则,ModelValidation 特性将收集整个类层次结构中的规则。

示例

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

此示例表明,使用自定义验证方法可以利用我们正在模型中的事实,因为我们使用其 items 关系来获取可能的冲突预订。这可能是其他方法难以实现的事情。

属性和消息可以自动自定义和本地化。只需为要自定义的项目定义区域文件。以下是为上面定义的 Booking 模型定义的示例

app/lang/en/bookings/attributes.php

<?php
  return [
    'title' => 'Title',
    'starts_at' => 'From',
    'ends_at' => 'To',
    'whole_days' => 'Whole days',
    'items' => 'Items'
  ];

app/lang/en/bookings/validation.php

<?php
  return [
    'items_ids.required' => "Please select at least one item.",
    'items_ids.available' => "Conflicts:\n:available",
    'items_ids.unavailable_item' => ":title [:resource] booked from :from to :to by :user"
  ];

无需污染全局 validation.php 文件以适应模型特定需求。

ExistingAttributesPersistence

已经看到这个了

class SomeModel extends BaseModel {
    protected $purgeable = [
        'temporary_attribute',
        'password_confirmation',
    ];
}

那么就忘记它吧,只需使用 ExistingAttributesPersistence 特性,并在您的模型类中不告诉任何东西。这个特性将在保存之前自动删除任何在数据库表上没有列对应关系的属性。

如何使用?使用特性,或者将其放入您的 BaseModel 中。

use Mortimer\Poignant\ExistingAttributesPersistence;

class SomeModel extends BaseModel {
  use ExistingAttributesPersistence;
}