peroks/model

模型:具有内置数据验证的类

1.0.0 2023-06-10 12:06 UTC

This package is auto-updated.

Last update: 2024-09-21 03:40:18 UTC


README

Model 类扩展了内置的 PHP ArrayObject 类,并添加了内部数据验证和 JSON 支持。

原因

模型可以在任何上下文中使用,但在 REST API 应用程序中尤其有用,在这些应用程序中,验证传入和传出数据通常既耗时又容易出错,难以阅读和维护。

您不必在项目中添加大量用于验证数据的代码,而是可以让模型根据它们的 属性定义自行验证。只需在 一个地方 定义每个模型的属性和约束,然后在应用程序中始终如一、高效且清晰地验证模型实例。

模型类似于 数据库表,其中每个 模型属性 对应于一个 表列。安装 Model Store 包可自动根据您的模型创建数据库表。

如何使用

创建模型类

对于每个模型,您可以在一个 单独的类 中定义模型属性和约束。每个模型类必须包含静态 $properties 属性并扩展 Model 类或其子类。

在下面的示例中,我们创建了一个包含两个属性(纬度经度)的地理点模型。这两个属性都是 必需的,并且它们必须是范围在 -90 到 90(纬度)和 -180 到 180(经度)之间的 浮点数

<?php

use Peroks\Model\Model;
use Peroks\Model\PropertyType;

/**
 * The GeoPoint model class.
 *
 * @property float $latitude The geo point latitude.
 * @property float $longitude The geo point longitude.
 */
class GeoPoint extends Model {

    /**
     * @var array An array of model properties.
     */
    protected static array $properties = [
        'latitude'  => [
            'id'       => 'latitude',
            'name'     => 'Latitude',
            'desc'     => 'The geo point latitude',
            'type'     => PropertyType::FLOAT,
            'required' => true,
            'min'      => -90,
            'max'      => 90,
        ],
        'longitude' => [
            'id'       => 'longitude',
            'name'     => 'Longitude',
            'desc'     => 'The geo point longitude',
            'type'     => PropertyType::FLOAT,
            'required' => true,
            'min'      => -180,
            'max'      => 180,
        ],
    ];
}

您可以将模型扩展为任何其他类。属性会从父类继承。

<?php

use Peroks\Model\PropertyType;

/**
 * The GeoPoint model with altitue. The altitude is optional.
 * 
 * @property float $altitude The geo point altitude.
 */
class GeoPointWithAltitude extends GeoPoint {
    
    /**
     * @var array An array of model properties.
     */
    protected static array $properties = [
        'altitude' => [
            'id'   => 'altitude',
            'name' => 'Altitude',
            'desc' => 'The geo point altitude',
            'type' => PropertyType::NUMBER, // int or float.
        ],
    ];
}

创建模型实例

创建模型实例有多种方法。模型构造函数接受关联数组、对象(包括模型实例)或 JSON 字符串。以下所有选项都会产生相同的结果。

$data = [ latitude => 70.6646625, longitude => 23.6807195 ];
$json = '{"latitude": 70.6646625, "longitude": 23.6807195}';

a) $geo = new GeoPoint( $data );
b) $geo = GeoPoint:create( $data );
c) $geo = GeoPoint:create( (object) $data );
d) $geo = GeoPoint:create( $json );
e) $geo = GeoPoint:create()->patch( $data );
f) $geo = GeoPoint:create()->replace( $data );
g) $geo = GeoPoint:load( 'geopoint.json' );

或者您可以直接创建一个空的模型,稍后再添加属性值。

$geo = new GeoPoint();
$geo->latitude  = 70.6646625;
$geo->longitude = 23.6807195;

ArrayObject 父类一样,您也可以像数组一样设置(和获取)模型属性。

$geo = new GeoPoint();
$geo['latitude']  = 70.6646625;
$geo['longitude'] = 23.6807195;

模型验证

由于每个模型都知道其属性定义和约束,因此验证模型非常简单。

$data = [ latitude => 70.6646625, longitude => 23.6807195 ];

// Returns the model instance on success or null on failure.
$geo = GeoPoint:create( $data )->validate(); // Returns the model instance.
$geo = GeoPoint:create()->validate(); // Returns null.
$geo = GeoPoint:create( [ latitude => 70.6646625 ] )->validate(); // Returns null

或者,您可以让验证在失败时抛出 ModelException

$data = [ latitude => 70.6646625, longitude => 23.6807195 ];

// Returns the model instance on success or throws a ModelException on failure.
$geo = GeoPoint:create( $data )->validate( true ); // Returns the model instance.
$geo = GeoPoint:create()->validate( true ); // Throws ModelExeption.
$geo = GeoPoint:create( [ latitude => 70.6646625 ] )->validate( true ); // Throws ModelExeption.

模型在创建时不会进行验证,只有当调用 Model::validate() 时才会进行验证。

获取模型数据

您可以像对象或数组一样访问模型数据

$geo = GeoPoint:create( [ latitude => 70.6646625, longitude => 23.6807195 ] );
$latitude  = $geo->latitude;
$longitude = $geo['longitude'];

或获取模型数据为关联数组。

$geo = GeoPoint:create( [ latitude => 70.6646625, longitude => 23.6807195 ] );
$data = $geo->data();

JSON 编码

您可以将模型轻松转换为 JSON。

$geo = GeoPoint:create( [ latitude => 70.6646625, longitude => 23.6807195 ] );
a) $json = json_encode( $geo );
b) $json = (string) $geo;

嵌套模型

模型可以包含其他模型。您只需将 model 和类名一起添加到一个 objectarray 属性中。

<?php

use Peroks\Model\Model;
use Peroks\Model\PropertyType;

/**
 * The Travel model.
 * 
 * @property GeoPoint $from Where the travel starts.
 * @property GeoPoint $to Where the travel ends.
 */
class Travel extends Model {
    
    /**
     * @var array An array of model properties.
     */
    protected static array $properties = [
        'from' => [
            'id'      => 'from',
            'name'    => 'From geo point',
            'type'    => PropertyType::OBJECT,
            'model'   => GeoPoint::class,
            'default' => [ latitude => 70.6646625, longitude => 23.6807195 ],
            'require' => true,
        ],
        'to' => [
            'id'      => 'to',
            'name'    => 'To geo point',
            'type'    => PropertyType::OBJECT,
            'model'   => GeoPoint::class,
            'default' => [ latitude => 59.8521293, longitude => 10.6590668 ],
            'require' => true,
        ],
    ];
}

如果您为子模型添加默认值,则当创建主模型时也会创建这些子模型。在验证时,子模型也会递归验证。

// Validates the travel model and all sub-models. 
$travel = Tarvel::create()->validate( true ); // Returns a valid Travel model.
$from   = $travel->from; // Returns a GeoPont model, already validated.

嵌套模型对于从 外部来源 导入 复杂数据结构 特别有用。使用模型,解码、转换和验证外部数据只需一行代码。

// Decode, convert and validate external data structures.
$json   = $client->import(); // Json encoded string from an api call.
$travel = Tarvel::create( $json )->validate( true );

支持的属性项

abstract class PropertyItem {
    const ID          = 'id';           // string, The property id (required).
    const NAME        = 'name';         // string, The property name (required).
    const DESC        = 'desc';         // string, The property description (default: null).
    const TYPE        = 'type';         // string, The property type (default: PropertyType::MIXED).
    const MODEL       = 'model';        // string, The class name of a model (default: null).
    const OBJECT      = 'object';       // string, The class or interface name to validate an object against (default: null).
    const FOREIGN     = 'foreign';      // string, The property contains an id of the (foreign) model class name (default: null).
    const DEFAULT     = 'default';      // mixed, The property default value (default: null).
    const REQUIRED    = 'required';     // bool, Whether the property is required or not (default: false).
    const READABLE    = 'readable';     // bool, Whether the property is readable or not (default: true).
    const WRITABLE    = 'writable';     // bool, Whether the property is writable or not (default: true).
    const MUTABLE     = 'mutable';      // bool, Whether the property is mutable (changeable) or not (default: true).
    const UNIQUE      = 'unique';       // bool, Whether the property value is unique or not (default: false).
    const INDEX       = 'index';        // bool, Whether the property is a db index or not (default: false).
    const PATTERN     = 'pattern';      // string, A regex pattern to validate a string value against (default: null).
    const ENUMERATION = 'enumeration';  // array, An enumeration of all valid property values (default: null).
    const MIN         = 'min';          // int|float, The minimum numeric value or string/array length (default: null).
    const MAX         = 'max';          // int|float, The maximum numeric value or string/array length (default: null).
    const VALUE       = 'value';        // mixed, The property value (default: null).
    const PROPERTIES  = 'properties';   // array, An array of model property definitions (default: null).
}

目前,foreignuniqueindex 属性项未使用。基于这些项没有进行验证。它们打算用于未来版本中创建数据库表。valueproperties 仅用于使用 Model::data( ModelData::PROPERTIES ) 导出模型数据。

支持的属性类型

abstract class PropertyType {
    const MIXED    = '';            // Any type, no validation.
    const BOOL     = 'boolean';
    const NUMBER   = 'number';      // Integer or float.
    const INTEGER  = 'integer';
    const FLOAT    = 'double';
    const STRING   = 'string';
    const UUID     = 'uuid';        // A uuid string.
    const URL      = 'url';         // A url.
    const EMAIL    = 'email';       // An email address.
    const DATETIME = 'datetime';    // An ISO 8601 datetime string.
    const DATE     = 'date';        // A date string (Y-m-d).
    const TIME     = 'time';        // A time string (H:i or H:i:s).
    const ARRAY    = 'array';
    const OBJECT   = 'object';
    const FUNCTION = 'function';    // A callable function.
}

安装

您需要 composer 来下载和安装此包。只需在您的项目中运行 composer require peroks/model