定制 stratadox/php-generics 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

stratadox/php-generics

Composer 安装命令:

composer require stratadox/php-generics

包简介

README 文档

README

Build Status Coverage Status Scrutinizer Code Quality

Albeit with some limitations and caveats, this library unlocks the power of generics for the PHP language.

Installation

Install with composer require stratadox/php-generics

Usage

First install the autoloader, using:

<?php
use Stratadox\PhpGenerics\Autoloader;
Autoloader::default()->install();

Making a new instance of a generic class

Given a generic base class Collection, with type parameter T.

To make a collection of strings, use:

<?php
namespace My\Name\Space;

use My\Generic\Collection;

var_dump(new Collection__string('foo', 'bar'));

To make a collection of Foo, use:

<?php
namespace My\Name\Space;

use My\Generic\Collection;
use My\Domain\Foo;

var_dump(new Collection__Foo(new Foo(), new Foo()));

Don't worry if the classes Collection__string and Collection__Foo don't exist (yet), they will be generated by the autoloader as soon as the code in question is executed.

There's no need to import either Collection__string or Collection__Foo in the aforementioned examples; the generated classes adopt the namespace of the calling code. The generic base class (in this case My\Generic\Collection) and the type arguments (My\Domain\Foo) do need to be imported, even if they are not (seemingly) used.

Only when type checking for the concrete generic class, the import is required:

<?php
namespace Some\Other\Name\Space;

use My\Name\Space\Collection__Foo;

function doSomething(Collection__Foo $list)
{
    // ...
}

Generics as type arguments

You can combine generics; for example:

<?php
namespace My\Name\Space;

use My\Generic\Collection;
use My\Domain\Foo;

var_dump(new Collection__Collection__Foo(
    new Collection__Foo(new Foo(), new Foo()),
    new Collection__Foo(new Foo(), new Foo()),
));

Making a new generic base class

To make a generic class, use the @Generic annotation. The annotation takes a count parameter, indicating the amount of generic types. You can use any class name in the Stratadox\PhpGenerics\Generic namespace as type parameter.

For example:

<?php
namespace My\Generic;

use Stratadox\PhpGenerics\Annotation\Generic;
use Stratadox\PhpGenerics\Generic\T;use function assert;

/** @Generic(count=1) */
class Collection extends \ArrayObject
{
    public function __construct(T ...$items)
    {
        parent::__construct($items);
    }

    public function offsetSet($index, $value): void
    {
        assert($value instanceof T);
        parent::offsetSet($index, $value);
    }
}

Enforcing return types

Php classes cannot alter their parent's return types if they have one, so it's not possible to declare a return type directly in the generic base class. Using the @ReturnType annotation, however, will resolve this issue:

<?php
namespace My\Generic;

use ArrayObject;
use Stratadox\PhpGenerics\Annotation\Generic;
use Stratadox\PhpGenerics\Annotation\ReturnType;
use Stratadox\PhpGenerics\Generic\T;

/** @Generic(count=1) */
class Collection extends ArrayObject
{
    public function __construct(T ...$items)
    {
        parent::__construct($items);
    }

    public function offsetSet($index, $value): void
    {
        assert(is_int($index) || null === $index);
        assert($value instanceof T);
        parent::offsetSet($index, $value);
    }

    /** @ReturnType(force="T") */
    public function offsetGet($index)
    {
        return parent::offsetGet($index);
    }
}

Generated concrete class

When the generic base class above is present and imported, an instantiation of Collection__string from within My\Name\Space will produce a file with the following contents:

<?php
namespace My\Name\Space;

use ArrayObject;
use My\Generic\Collection;
use Stratadox\PhpGenerics\Annotation\ReturnType;

final class Collection__string extends Collection
{
    public function __construct(string ...$items)
    {
        ArrayObject::__construct($items);
    }

    public function offsetSet($index, $value): void
    {
        assert(is_int($index) || null === $index);
        assert(is_string($value));
        ArrayObject::offsetSet($index, $value);
    }

    /** @ReturnType(force="T") */
    public function offsetGet($index): string
    {
        return ArrayObject::offsetGet($index);
    }
}

Inner workings

This library uses some of the darkest magic available to php. In short, it hacks into the auto-loading process, adding a loader that peeks into the stack trace and subsequently dissects the source code of the file that instantiated the generic class, in order to harvest the namespace and imports and produce a new concrete class that satisfies the type arguments. Generating the class involves even more magic, in the sense that the generic base class is parsed into an abstract syntax tree, modified to accommodate the type arguments and transformed back again into php code, to be written to a file somewhere.

TL;DR: Black magic.

Limitations

  • The < and > symbols are (for now?) illegal for use in class names in php, even when cheating. Please prove me wrong. Until you do, we'll have to make do with legal characters, the default currently being a double underscore.

  • You can use each type combination only for that specific type combination per namespace. If that did not sound confusing I'll eat my hat, so let me explain:

    • A concrete class is generated for a combination of generic base class and type arguments
    • The concrete class takes the namespace it was created in
    • The type arguments' FQCN's are extracted from the imports of the calling file
    • Type arguments are referenced by their short name in the concrete class name

    Following from the above, if you're in the namespace Acme\Foo and import Foo\Bar, you could build a Collection__Bar as typed list of Foo\Bar objects. If, in another file in the same namespace, Baz\Bar were used and a Collection__Bar were instantiated, the collection may or may not reject the Baz\Bar objects, depending on whether or not Collection__Bar was already loaded as a collection of Foo\Bar objects. This limitation can be circumvented by using an alias, e.g. use Baz\Bar as BazBar, and accordingly Collection__BazBar.

  • No inheritance in generic types. Although you can pass subtypes into a type hinted concrete class, a collection of subclasses will not inherit from the collection of parents.

    Given a parent class Thing and a child class SpecialThing, one may pass a SpecialThing into a Collection__Thing. The Collection__SpecialThing, however, does not extend from Collection__Thing. When a Collection__Thing is required, the Collection__SpecialThing will not satisfy.

    The reason for the limitation is that a Map__SpecialThing__SpecialThing would have to extend both Map__Thing__SpecialThing and Map__SpecialThing__Thing, but multiple inheritance isn't allowed. A potential solution / bypass could be to generate and implement both _Map__Thing__SpecialThing and _Map__SpecialThing__Thing (and _Map_Thing_Thing), so that type checks would be possible albeit with prefix. Might want to have that before v1.0.

stratadox/php-generics 适用场景与选型建议

stratadox/php-generics 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 3 次下载、GitHub Stars 达 2, 最近一次更新时间为 2020 年 03 月 04 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 stratadox/php-generics 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 stratadox/php-generics 我们能提供哪些服务?
定制开发 / 二次开发

基于 stratadox/php-generics 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 3
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 7
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2020-03-04