我正在使用Nicolas Widart的Laravel Modules软件包来帮助管理大型应用程序,并将所有内容分成逻辑模块。我希望能够使用不同的模块,并且无需任何额外配置即可轻松播放。
我的所有模块都将定义接口和默认实现,允许应用程序(控制加载哪些模块的系统)通过依赖注入来指定它想要使用特定的实现。
我能够通过让一些模块需要其他模块来做出一些假设,例如支付处理模块(模块PP)可以假设支付与用户相关联(用户的接口在另一个模块中定义) ,模块U)。
我理想的情况是我可以添加到另一个必需模块中定义的现有PHP接口。例如,能够从模块U中定义的存储库中检索用户,并在其上调用模块PP中定义的方法。
一旦模块PP将模块U中的接口(再次通过依赖注入)解析为类,我希望模块PP中的方法可以在该类上调用。
我已经能够使用__call
魔术方法实现此目的。
此模块定义要添加到现有界面的核心操作。
<?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);
}
<?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);
}
}
}
}
<?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;
}
}
<?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的PHPDoc的方法。
我研究了Decorator模式,但这感觉 clunky 因为我总是需要依赖每个模块中的新实现,而不是仅仅添加到现有模块中。< / p>
我意识到这是一个很大的问题,所以我真诚地感谢任何愿意看一眼的人!
答案 0 :(得分:1)
查看Laravel的macroable
特质。它基本上是相同的想法,Laravel在整个地方使用它。
所以是的,它可以扩展到某一点。像其他几乎一样,这是一个可以被滥用的工具。使用它有一点常识,你应该没问题。