【发布时间】:2017-08-20 05:09:56
【问题描述】:
我正在使用 Nicolas Widart 的 Laravel Modules 包来帮助管理大型应用程序,并将所有内容分成逻辑模块。我希望能够放入不同的模块并让它们在没有任何额外配置的情况下很好地发挥作用。
我的所有模块都将定义接口和默认实现,允许应用程序(控制加载哪些模块的系统)通过依赖注入指定它想要使用特定实现。
我可以通过让一些模块需要其他模块来做出一些假设,例如支付处理模块(模块 PP)可以假设支付与用户相关联(用户的接口在另一个模块中定义) , 模块 U)。
我的理想方案是我可以添加到在另一个必需模块中定义的现有 PHP 接口。例如,能够从模块 U 中定义的存储库中检索用户并调用模块 PP 中定义的方法。
一旦 Module PP 将 Module U 的接口(同样,通过依赖注入)解析为一个类,我希望 Module PP 中的方法可以在该类上调用。
我已经能够使用下面的__call 魔术方法来实现这一点。
扩展模块
此模块定义要添加到现有接口的核心操作。
IsExtendable 接口
<?php
namespace Modules\Extensions\Contracts;
interface IsExtendable
{
/**
* Get the list of extensions for this entity.
*
* @return array
*/
public static function getExtensions();
/**
* Adds an extension to this entity.
*
* @param string $name
* @param mixed $function
*/
public static function addExtension($name, $function);
/**
* Checks whether the entity has the given extension.
*
* @param string $name
*
* @return bool
*/
public static function hasExtension($name);
/**
* Call the extension if it exists, or pass it further up the chain.
*
* @param string $name
* @param mixed $arguments
*
* @return mixed
*/
public function __call($name, $arguments);
}
IsExtendable 特性
<?php
namespace Modules\Extensions;
trait IsExtendable
{
/** @var $extensions */
private static $extensions = [];
/**
* Get the list of extensions for this entity.
*
* @return array
*/
public static function getExtensions()
{
return self::$extensions;
}
/**
* Adds an extension to this entity.
*
* @param string $name
* @param mixed $function
*/
public static function addExtension($name, $function)
{
if(is_callable($function) == FALSE)
{
throw new \InvalidArgumentException('Function must be callable.');
}
self::$extensions[$name] = $function;
}
/**
* Checks whether the entity has the given extension.
*
* @param string $name
*
* @return bool
*/
public static function hasExtension($name)
{
return array_key_exists($name, self::getExtensions()) == TRUE;
}
/**
* Calls the extension if it exists, or passes it further up the chain.
*
* @param string $name
* @param mixed $arguments
*
* @return mixed
*/
public function __call($name, $arguments)
{
if(self::hasExtension($name) == TRUE)
{
$callable = self::getExtensions()[$name];
return call_user_func_array($callable, array_merge(array($this), $arguments));
}
else
{
return parent::__call($name, $arguments);
}
}
}
服务提供者
<?php
namespace Modules\Extensions\Providers;
use Illuminate\Support\ServiceProvider;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
class ExtensionServiceProvider extends ServiceProvider
{
/**
* @param string $implementation
* @param string $functionName
*
* @return callable
*/
public function prepareExtension($implementation, $functionName)
{
return $implementation . '::' . $functionName;
}
/**
* @param string $contract
* @param string $implementation
*
* @return void
*/
public function extractExtensions($contract, $implementation)
{
$reflection = new \ReflectionClass($implementation);
$methods = [];
foreach($reflection->getMethods(\ReflectionMethod::IS_STATIC) as $method)
{
// TODO: May be able to use $method->getClosure() here
// https://stackoverflow.com/questions/8299886/php-get-static-methods
$methods[] = $method->getName();
}
$this->registerExtensions($contract, $methods, $implementation);
}
/**
* @param string $contract
* @param string $name
* @param string $function
*
* @return void
*/
public function registerExtension($contract, $name, $function)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
$base::addExtension($name, $function);
}
}
/**
* @param string $contract
* @param array $extensions
* @param string|null $implementation
*
* @return void
*/
public function registerExtensions($contract, array $extensions = [], $implementation = NULL)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
foreach($extensions as $name => $function)
{
if(is_int($name) == TRUE)
{
if(is_string($function) == TRUE)
{
$name = $function;
}
else
{
throw new \InvalidArgumentException('All extensions must have a valid name.');
}
}
if(is_string($function) == TRUE)
{
if(strpos($function, '::') === FALSE && $implementation != NULL)
{
$function = $this->prepareExtension($implementation, $function);
}
}
$base::addExtension($name, $function);
}
}
}
}
模块 U
用户界面
<?php
namespace Modules\Auth\Contracts\Entities;
interface User
{
/**
* @return int
*/
public function getId();
/**
* @return string
*/
public function getName();
/**
* @return string
*/
public function getEmail();
/**
* @return \DateTime
*/
public function getCreatedAt();
/**
* @return \DateTime
*/
public function getUpdatedAt();
}
用户实现
<?php
namespace Modules\Auth\Entities;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Extensions\IsExtendable;
class User implements
IsExtendableContract,
UserContract
{
use IsExtendable;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
}
模块 PP
用户扩展
<?php
namespace Modules\Test\Entities\Extensions;
use Modules\Auth\Contracts\Entities\User;
class UserExtension
{
/**
* @param User $context
*/
public static function getCardLastFour($context)
{
return $context->card_last_four;
}
/**
* @param User $context
*/
public static function getCardBrand($context)
{
return $context->card_brand;
}
/**
* @param User $context
*/
public static function getStripeId($context)
{
return $context->stripe_id;
}
}
服务提供者
<?php
namespace Modules\Test\Providers\Extensions;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Test\Entities\Extensions\UserExtension;
use Modules\Extensions\Providers\ExtensionServiceProvider;
class StripeExtensionProvider extends ExtensionServiceProvider
{
public function boot()
{
// TODO: Set the contract as a static field on the extension to then automatically extract from all extension files in a folder
$this->extractExtensions(UserContract::class, UserExtension::class);
}
}
我的问题是,这种方法是否可扩展(可能跨越 10 个模块),您能预见到它的任何问题吗? 或者有更好/更流行(和受支持)的方法来做到这一点?我不想花 2 年时间完成一个项目,然后发现我真的很讨厌我实施它的方式。
我知道这个概念不支持开箱即用的 IDE 自动补全,但我可以构建一种类似于this package 的 PHPDocs 生成方式。
我研究了Decorator 模式,但这感觉笨拙,因为我总是需要依赖每个模块中的新实现,而不是仅仅添加到现有的。
我知道这是一个很大的问题,所以我真诚地感谢任何愿意看看它的人!
【问题讨论】:
标签: php laravel laravel-5 module