peroks / model
模型:具有内置数据验证的类
Requires
- php: ^8.1
Requires (Dev)
- phpunit/phpunit: ^10
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
和类名一起添加到一个 object
或 array
属性中。
<?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).
}
目前,foreign
、unique
和 index
属性项未使用。基于这些项没有进行验证。它们打算用于未来版本中创建数据库表。value
和 properties
仅用于使用 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
。