kael-shipman / factory
一个通过声明式方法调用提供可覆盖实例化的类。
Requires
- php: >=5.5
Requires (Dev)
- phpunit/phpunit: ~6.0
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();
这是一个非常高级的示例,但它表明
- 您告诉工厂您想要什么类型的对象(我的实现实际上还允许子类型);
- 您验证返回的实例实现了您期望的接口。
您通常会在代码的更深处找到更复杂的示例。考虑以下示例
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
)。
如果你正在编写可能被他人扩展的内容,将自定义工厂放入自己的类文件中是个不错的选择。这样,他人可以扩展它,而无需重新映射你已经映射的所有对象。
结论
就是这样!希望对某人有所帮助。