kael-shipman/factory

此包已废弃,不再维护。未建议替代包。

一个通过声明式方法调用提供可覆盖实例化的类。

v5.0.2 2017-09-12 16:18 UTC

README

已废弃

此项目已不再有用。为了有用的类型检查,抽象工厂类应使用显式方法名进行实例化,而不是基于字符串的实例化。这一事实否定了此类曾经提供的任何好处。

对于那些希望找到解决方案的人来说,正确的答案可能如下所示

<?php
// FactoryInterface.php
namespace YourNamespace;

interface FactoryInterface {
    public function newMyObject();
    public function newYourObject();
}
?>


<?php
// Factory.php
namespace YourNamespace;

class Factory implements FactoryInterface {
    public function newMyObject() {
        return new MyObject(func_get_args()); 
    }

    public function newYourObject() {
        return new \OtherNamespace\YourObject(func_get_args());
    }
}
?>

您可以使用相同的技术来执行静态方法,获取静态类常量等。关于在工厂类中实际实现什么,界线并不明确,但您可以根据自己的感受来判断。

一个基于文本描述符而不是具体类名提供可覆盖实例化的类。

如果您不了解依赖注入,互联网上有许多关于它的资源。请在使用它之前确保您理解这是什么。

简而言之,您应该找到一种方法,让您和其他程序员能够在任何给定时间更改所需功能的实际实现,而无需更改使用它的代码。

我选择通过创建一个传递给所有其他类的 Factory 类来实现这一点。当我的某个对象需要创建子对象时,它会告诉工厂它想要什么,给出它想要传递给构造函数的参数,然后工厂会给出一个由程序员在应用程序配置时定义的类的实例。

程序本身并不总是知道这个对象的实际类是什么,实际上也不应该关心。它应该关心的是,无论它得到什么对象,它都实现了它期望的接口。

使用方法

以下是一个基本示例,说明如何使用此方法(实现见下方)

// Get your factory instance
$f = MyFactory::getInstance();

// Use it to create a new app instance
$app = $f->new('app');

// Since you can't guarantee *what* app instance it gave you, you have to verify that it implements the interface you're expecting
if (!($app instanceof AppIWannaRun)) throw new RuntimeException("App returned by factory must be of type `AppIWannaRun`");

// Now run it
$app->run();

这是一个非常高级的示例,但它表明

  1. 您告诉工厂您想要什么类型的对象(我的实现实际上还允许子类型);
  2. 您验证返回的实例实现了您期望的接口。

您通常会在代码的更深处找到更复杂的示例。考虑以下示例

class MyApp {
    protected $factory;
    protected $db;

    // Here, you have to pass an instance of your factory to the app on construct, along with an instance of a DB
    public function __construct(FactoryInterface $f, DatabaseInterface $db) {
        $this->factory = $f;
        $this->db = $db;
    }

    // Do lots of stuff here ......

    // Now here's an internal method
    protected function getInternalThings() {
        // Get things from the DB
        $things = $this->db->query('SELECT * FROM `things`');

        // Create a thing collection
        $thingCollection = $this->factory->new('collection', 'things');

        // Iterate over the db results, adding things to the collection
        foreach($things as $thing) {
            $thingCollection[] = $this->factory->create('thing', null, $thing);
        }

        // Return the collection
        return $thingCollection;
    }

    // ...
}

在上面的示例中,我们使用工厂来实例化一个集合,然后使用其特殊的“create”方法(该方法仅适用于实现了静态“create”方法的类)从数据中“恢复”事物的实例。这是因为它告诉工厂哪个类代表“事物集合”,哪个类代表“事物”。

您是这样做的...

创建您的类型映射

在上面的示例中,我们请求了类型为 app 的对象,类型为 collection 且子类型为 things 的对象,以及多个类型为 thing 的对象。但工厂并不是天生就知道如何创建这些对象。您必须告诉它。您通过 子类化 工厂并重写 getClass 方法来完成此操作。

为了创建可以实例化所有这些对象的工厂,我们这样做

<?php
namespace MyNamespace;

class MyFactory extends \KS\Factory {
    // Signature has to match the original Factory::getClass method
    public function getClass(string $type, string $subtype=null) {
        // Return the class as a string including namespace
        if ($type == 'app') {
            // Always a good practice to check for subtype, even if you don't need one today
            if (!$subtype || $subtype == 'generic') return '\\MyNamespace\\App';
        }

        if ($type == 'collection') {
            if ($subtype == 'thing') return '\\MyNamespace\\ThingsCollection';
            // Notice, if we're asking for a subtype we don't know how to create, it falls through
        }

        if ($type == 'thing') {
            if (!$subtype || $subtype == 'generic') return '\\MyNamespace\\Thing';
        }

        // Fall through to parent
        return parent::getClass($type, $subtype);
    }
}

有时您会将其放在一个单独的文件中。然而,更常见的是,这可以直接放在您的入口控制器中(通常是Web应用程序中的 index.php)。

如果你正在编写可能被他人扩展的内容,将自定义工厂放入自己的类文件中是个不错的选择。这样,他人可以扩展它,而无需重新映射你已经映射的所有对象。

结论

就是这样!希望对某人有所帮助。