【问题标题】:Does this concept to add methods to an existing PHP interface scale?将方法添加到现有 PHP 接口的这个概念是否可以扩展?
【发布时间】: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


    【解决方案1】:

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

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

    【讨论】:

    • 感谢您的快速回复,让我印象深刻的是,我的概念与内置的概念如此接近,以至于我不知道存在!我会看看如何将其调整到我的项目中,并将问题留待更长时间,看看是否有其他人有任何建议 - 谢谢。
    猜你喜欢
    • 1970-01-01
    • 2010-09-19
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 2023-03-18
    • 1970-01-01
    相关资源
    最近更新 更多