spindle/types

PHP对象系统的基本类型

1.0.4 2016-04-30 04:09 UTC

This package is not auto-updated.

Last update: 2024-09-14 15:32:20 UTC


README

Build Status Scrutinizer Code Quality Code Coverage Latest Stable Version Total Downloads Latest Unstable Version License

PHP通过提供基础类群来实现强类型。

$ composer require 'spindle/types:*'

基本类型

Spindle\Types\Enum

继承Enum后变为枚举类型。实例可以保证是类中定义的const的某个值。

<?php

final class Suit extends Spindle\Types\Enum
{
    const SPADE   = 'spade'
       ,  CLUB    = 'club'
       ,  HEART   = 'heart'
       ,  DIAMOND = 'diamond'
}

$spade = new Suit(Suit::SPADE);
$spade = Suit::SPADE(); //syntax sugar

echo $spade, PHP_EOL;
echo $spade->valueOf(), PHP_EOL;

function doSomething(Suit $suit)
{
    //$suitは必ず4種類のうちのどれかである
}

Spindle\Types\TypedObject

继承TypedObject后可以创建具有固定属性类型的类。可以更安全地处理复杂数据。可用于实现领域驱动设计中的"实体"或"值对象"。

类型通过静态方法schema()定义。schema需要返回一个数组,该数组是重复的,格式为属性名 => 类型, 默认值。默认值可以省略,此时将设置为null。

<?php
class User extends Spindle\Types\TypedObject
{
    static function schema()
    {
        return array(
            'firstName' => self::STR,
            'lastName'  => self::STR,
            'age'       => self::INT,
            'birthday'  => 'DateTime', new DateTime('1990-01-01'),
        );
    }

    function checkErrors()
    {
        $errors = array();
        if ($this->age < 0) {
            $errors['age'] = 'ageは0以上である必要があります';
        }

        return $errors;
    }
}

$taro = new User;
$taro->firstName = 'Taro';
$taro->lastName = 'Tanaka';
$taro->age = 20;

//$taro->age = '20'; とすると、InvalidArgumentExceptionが発生して停止する

以下是可以指定的类型值。

  • self::BOOL (布尔值)
  • self::INT (整数)
  • self::DBL (浮点数)
  • self::STR (字符串)
  • self::ARR (数组)
  • self::OBJ (对象)
  • self::RES (资源)
  • self::CALL (回调函数)
  • self::MIX (未指定类型)
  • className 类名/接口名。使用完全限定名指定。对于类名,使用instanceof进行判定。

由于固定了__get()__set()为final,继承TypedObject将部分剥夺类的能力。不建议所有类都从TypedObject派生。

TypedObject支持foreach (IteratorAggregate)。TypedObject可以通过count()函数计算元素数量。(Countable)

推荐实现checkErrors()方法。这是一个执行无法通过类型检查的验证的方法。默认实现检查所有属性是否为not null。

TypedObject::$preventExtensions

TypedObject默认情况下拒绝schema中未定义的属性的赋值和引用。这有助于更容易地发现属性错误,但也可能不方便。

将TypedObject::$preventExtensions设置为false时,不拒绝未定义的属性,而是自动扩展。(默认为true)

此外,扩展的属性将自动被处理为mixed(不进行类型检查)。

<?php
use Spindle\Types;

class MyObj extends Types\TypedObject
{
    static function schema()
    {
        return array(
            'a' => self::INT,
            'b' => self::BOOL,
        );
    }
}

$obj = new MyObj;

Types\TypedObject::$preventExtensions = false;
$obj->c = 'str'; //エラーは起きない

Types\TypedObject::$preventExtensions = true;
$obj->c = 'str'; //例外発生

TypedObject::$casting

TypedObject在属性赋值时,如果schema和类型不匹配,则抛出异常。但是,有时也想要像PHP标准行为一样,在尝试赋值不同类型时执行类型转换。例如,从数据库中获取字符串并恢复对象。

将TypedObject::$casting设置为true时,即使尝试赋值不同类型,也会尽可能进行转换。

PDO::FETCH_CLASS的配合

可以将从数据库SELECT获取的结果流到TypedObject中。PDO有一个标准功能,可以直接使用PDO::FETCH_CLASS模式来创建类实例,因此建议使用它。通常,从PDO返回的数据是string类型,因此请确保启用$casting选项。

<?php
use Spindle\Types;

class User extends Types\TypedObject
{
    static $casting = true;

    static function schema()
    {
        return array(
            'userId' => self::INT,
            'name' => self::STR,
            'age' => self::INT
        );
    }
}

$pdo = new PDO('sqlite::memory:', null, null, array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
));
$pdo->exec('CREATE TABLE User(userId INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)');
$pdo->exec('INSERT INTO User(name, age) VALUES("taro", 20)');
$pdo->exec('INSERT INTO User(name, age) VALUES("hanako", 21)');

$stmt = $pdo->prepare('SELECT * FROM User');
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, __NAMESPACE__ . '\\RowModel');
$stmt->execute();

foreach ($stmt as $row) {
    self::assertInternalType('integer', $row->userId);
    self::assertInternalType('string',  $row->name);
    self::assertInternalType('integer', $row->age);
}

需要注意的是,PDO::FETCH_CLASS的行为与常规对象初始化和操作不同,它首先执行setter,然后启动构造函数。由于TypedObject在构造函数中初始化对象,因此这种操作可能不会正常工作。

使用PDO::FETCH_CLASS时,必须同时指定PDO::FETCH_PROPS_LATE。此选项指定后,构造函数将先启动,从而正常工作。

TypedObject的继承

继承由TypedObject创建的类时,父类的schema不会自动继承。需要使用extend方法显式扩展。

<?php
class Employee extends Spindle\Types\TypedObject
{
    static function schema()
    {
        return array(
            'id' => self::INT, 0,
            'name' => self::STR,
        );
    }
}

class Boss extends Employee
{
    static function schema()
    {
        return self::extend(parent::schema(), array(
            'room' => self::INT,
        ));
    }
}

Spindle\Types\ConstObject

ConstObject是使TypedObject变为不可变对象的装饰器。

<?php
$const = new ConstObject($typedObject);

echo $const->foo; //参照は透過的に可能
//$const->foo = 'moo'; どのプロパティに対しても代入操作は常に例外を発生させる

Spindle\Types\Collection

对array()添加了一些限制的数组。

  • 仅允许数值索引
  • 顺序得到保证
  • 可以按需固定元素的类型

Spindle\Types\ConstCollection

将Collection变为只读的装饰器。

Polyfill

PHP从5.4和5.5开始可以使用一些标准接口。为了在PHP5.3中使用它们,提供了Polyfill。

为了确保如DateTime implements DateTimeInterface等状态,它们被放置在自定义命名空间上。

Spindle\Types\Polyfill\JsonSerializable

相当于PHP5.4以后的JsonSerializable接口。

Spindle\Types\Polyfill\DateTimeInterface

相当于PHP5.5以后的DateTimeInterface接口。

Spindle\Types\Polyfill\DateTime

实现了DateTimeInterface的DateTime。

Spindle\Types\Polyfill\DateTimeImmutable

相当于PHP5.5以后的DateTimeImmutable。不能修改状态,在modify或setTimestamp等方法被调用时,将返回一个新的实例。

许可

spindle/types的版权已放弃。使用时没有限制,不需要联系作者或显示版权声明。即使是代码片段也可以自由复制使用。

许可原文

CC0-1.0 (无权利保留)