这个概念是否将方法添加到现有的PHP接口规模?

时间:2017-03-27 13:07:18

标签: php laravel laravel-5 module

我正在使用Nicolas Widart的Laravel Modules软件包来帮助管理大型应用程序,并将所有内容分成逻辑模块。我希望能够使用不同的模块,并且无需任何额外配置即可轻松播放。

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

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

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

一旦模块PP将模块U中的接口(再次通过依赖注入)解析为类,我希望模块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 Trait

<?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的PHPDoc的方法。

我研究了Decorator模式,但这感觉 clunky 因为我总是需要依赖每个模块中的新实现,而不是仅仅添加到现有模块中。< / p>

我意识到这是一个很大的问题,所以我真诚地感谢任何愿意看一眼的人!

1 个答案:

答案 0 :(得分:1)

查看Laravel的macroable特质。它基本上是相同的想法,Laravel在整个地方使用它。

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