这个概念将方法添加到现有的PHP接口中是否可扩展?



我正在使用Nicolas Widart的Laravel模块包来帮助管理大型应用程序,并将所有内容分离到逻辑模块中。我希望能够放入不同的模块并让它们在没有任何额外配置的情况下很好地运行。

我的所有模块都将定义接口和默认实现,允许应用程序(控制加载哪些模块的系统)指定它希望通过依赖注入来改用特定实现。

我能够通过让某些模块需要其他模块来做出一些假设,例如支付处理模块(模块 PP)可以假设支付与用户绑定(用户的接口在另一个模块模块 U 中定义)。

我的理想方案是我可以添加到另一个必需模块中定义的现有 PHP 接口。例如,能够从模块 u 中定义的存储库中检索用户,并对其调用在模块 PP 中定义的方法。

一旦模块 PP 将接口(再次通过依赖注入)从模块 u 解析到类,我希望模块 PP 中的方法可以在该类上调用。

我已经能够使用如下__call魔术方法实现这一目标。


扩展模块

此模块定义要添加到现有接口的核心操作。

可扩展接口

<?php
namespace ModulesExtensionsContracts;
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 ModulesExtensions;
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 ModulesExtensionsProviders;
use IlluminateSupportServiceProvider;
use ModulesExtensionsContractsIsExtendable 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 ModulesAuthContractsEntities;
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 ModulesAuthEntities;
use ModulesExtensionsContractsIsExtendable as IsExtendableContract;
use ModulesAuthContractsEntitiesUser as UserContract;
use ModulesExtensionsIsExtendable;
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 ModulesTestEntitiesExtensions;
use ModulesAuthContractsEntitiesUser;
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 ModulesTestProvidersExtensions;
use ModulesAuthContractsEntitiesUser as UserContract;
use ModulesTestEntitiesExtensionsUserExtension;
use ModulesExtensionsProvidersExtensionServiceProvider;
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 自动完成,但我可以构建一种生成类似于这个包的 PHPDocs 的方式。

我已经研究了装饰器模式,但这感觉很笨拙,因为我总是需要依赖每个模块中的新实现,而不仅仅是添加到现有模块中。

我意识到这是一个大问题,所以我衷心感谢任何愿意看它的人!

看看Laravel的macroable特质。这基本上是同样的想法,Laravel到处都在使用它。

所以是的,它可以扩展到某个点。像几乎所有其他东西一样,这是一个可能被滥用的工具。用一点常识使用它,你应该没问题。

最新更新