承接 dhii/module-interface 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

dhii/module-interface

Composer 安装命令:

composer require dhii/module-interface

包简介

Interfaces for modules

README 文档

README

Continuous Integration Latest Stable Version Latest Unstable Version

Details

This package contains interfaces that are useful in describing modules and their attributes and behaviour.

Requirements

  • PHP: 7.1 and up, until 8 inclusive,

Interfaces

  • ModuleInterface - The interface for a module. A module is an object that represents an application fragment. Modules are prepared using setup(), which returns a ServiceProviderInterface instance that the application may consume, and invoked using run(), consuming the application's DI container.
  • ModuleAwareInterface - Something that can have a module retrieved.
  • ModuleExceptionInterface - An exception thrown by a module.

Usage

Module Package

In your module's pacakge, create a file that returns a module factory. This factory MUST return an instance of ModuleInterface from this pacakge. By convention, this file has the name module.php, and is located in the root directory. Below is a very basic example. In real life, the service provider and the module will often have named classes of their own, and factories and extensions will be located in services.php and extensions.php respectively, by convention.

// module.php
use Dhii\Modular\Module\ModuleInterface;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;

return function () {
    return new class () implements ModuleInterface {
    
        /**
         * Declares services of this module.
         * 
         * @return ServiceProviderInterface The service provider with the factories and extensions of this module.
         */
        public function setup() : ServiceProviderInterface
        {
            return new class () implements ServiceProviderInterface
            {
                /**
                 * Only the factory of the last module in load order is applied. 
                 * 
                 * @return array|callable[] A map of service names to service definitions.
                 */
                public function getFactories()
                {
                    return [
                        // A factory always gets one parameter: the container.
                        'my_module/my_service' => function (ContainerInterface $c) {
                            // Create and return your service instance
                            return new MyService(); 
                        },
                    ];
                }
                
                /**
                 * All extensions are always applied, in load order. 
                 * 
                 * @return array|callable[] A map of service names to extensions.
                 */            
                public function getExtensions()
                {
                    return [
                        // An extension gets an additional parameter:
                        // the value returned by the factory or the previously applied extensions.
                        'other_module/other_service' => function (
                            ContainerInterface $c,
                            OtherServiceInterface $previous
                        ): OtherServiceInterface {
                            // Perhaps decorate $previous and return the decorator
                            return new MyDecorator($previous);
                        },
                    ];
                }
            };    
        }

        /**
         * Consumes services of this and other modules.
         * 
         * @param ContainerInterface $c A container with the services of all modules.
         */
        public function run(ContainerInterface $c): void
        {
            $myService = $c->get('my_module/my_service');
            $myService->doSomething();
        }       
    };
};

In the above example, the module declares a service my_module/my_service, and an extension for the other_module/other_service, which may be found in another module. Note that by convention, the service name contains the module name prefix, separated by forward slash /. It's possible to further "nest" services by adding slash-separated "levels". In the future, some container implementations will add benefits for modules that use this convention.

Applications would often need the ability to do something with the arbitrary set of modules they require. In order for an application to be able to group all modules together, declare the package type in your composer.json to be dhii-mod by convention. Following this convention would allow all modules written by all authors to be treated uniformly.

{
    "name": "me/my_module",
    "type": "dhii-mod"
}

What's important here:

  1. A module's setup() method should not cause side effects.

    The setup method is intended for the modules to prepare for action. Modules should not actually peform the actions during this method. The container is not available in this method, and therefore the module cannot use any services, whether of itself or of other modules, in this method. Do not try to make the module use its own services here.

  2. Implement the correct interfaces.

    A module MUST implement ModuleInterface. The module's setup() method MUST return ServiceProviderInterface. Even though the Service Provider standard is experimental, and has been experimental for a long time, the module standard relies heavily on it. If the module standard becomes ubiquitous, this could push FIG to go forward with the Service Provider standard, hopefully making it into a PSR.

  3. Observe conventions.

    It is important that conventions outlined here are observed. Some are necessary for smooth operation of modules and/or consuming applications. Some others may not make a difference right now, but could add benefits in the future. Please observe these conventions to ensure an optimal experience for yourself and for other users of the standard.

Consumer Package

Module Installation

The package that consumes modules, which is usually the application, would need to require the modules. The below example uses the oomphinc/composer-installers-extender lib to configure Composer so that it installs all dhii-mod packages into the modules directory in the application root. Packages me/my_module and me/my_other_module would therefore go into modules/me/my_module and modules/me/my_other_module respectively.

{
  "name": "me/my_app",
  "require": {
    "me/my_module": "^0.1",
    "me/my_other_module": "^0.1",
    "oomphinc/composer-installers-extender": "^1.1"
  },
  
  "extra": {
    "installer-types": ["dhii-mod"],
    "installer-paths": {
      "modules/{$vendor}/{$name}": ["type:dhii-mod"]
    }
  }
}
Module Loading

Once a module has been required, it must be loaded. Module files must be explicitly loaded by the application, because the application is what determines module load order. The load order is the fundamental principle that allows modules to extend and override each other's services in a simple and intuitive way:

  1. Factories in modules that are loaded later will completely override factories of modules loaded earlier.

    Ultimately, for each service, only one factory will be used: the one declared last. So if my_other_module is loaded after my_module, and it declares a service my_module/my_service, then it will override the my_module/my_service service declared by my_module. In short: last factory wins.

  2. Extensions in modules that are loaded later will be applied after extensions of modules loaded earlier.

    Ultimately, extensions from all modules will be applied on top of what is returned by the factory. So if my_other_module declares an extension other_module/other_service, it will be applied after the extension other_module/other_service declared by my_module. In short: later extensions extend previous extensions.

Continuing from the examples above, if something in the application requests the service other_module/other_service declared by my_other_module, this is what is going to happen:

  1. The factory in my_other_module is invoked.
  2. The extension in my_module is invoked, and receives the result of the above factory as $previous.
  3. The extension in my_other_module is invoked, and receives the result of the above extension as $previous
  4. The caller of get('other_module/other_service') receives the result of the above extension.

Thus, any module can override and/or extend services from any other module. Below is an example of what an application's bootstrap code could look like. This example uses classes from dhii/containers.

// bootstrap.php

use Dhii\Modular\Module\ModuleInterface;
use Interop\Container\ServiceProviderInterface;
use Dhii\Container\CompositeCachingServiceProvider;
use Dhii\Container\DelegatingContainer;
use Dhii\Container\CachingContainer;

(function ($file) {
    $baseDir = dirname($file);
    $modulesDir = "$baseDir/modules";
    
    // Order is important!
    $moduleNames = [
        'me/my_module',
        'me/my_other_module',
    ];
    
    // Create and load all modules
    /* @var $modules ModuleInterface[] */
    $modules = [];
    foreach ($moduleNames as $moduleName) {
        $moduleFactory = require_once("$modulesDir/$moduleName/module.php");
        $module = $moduleFactory();
        $modules[$moduleName] = $module;
    }

    // Retrieve all modules' service providers
    /* @var $providers ServiceProviderInterface[] */
    $providers = [];
    foreach ($modules as $module) {
        $providers[] = $module->setup();
    }
    
    // Group all service providers into one
    $provider = new CompositeCachingServiceProvider();
    $container = new CachingContainer(new DelegatingContainer($provider, $parentContainer = null));

    // Run all modules
    foreach ($modules as $module) {
        $module->run($container);
    }
})(__FILE__);

The above will load, setup, and run modules me/my_module and me/my_other_module, in that order, from the modules directory, provided that conventions have been followed by those modules. What's important to note here:

  1. First all modules are set up, and then all modules are run.

    If you set up and run modules in the same step, it will not work, because the bootstrap will not have the opportunity to configure the application's DI container with services from all modules.

  2. The CompositeCachingServiceProvider is what is responsible for resolving services correctly.

    This relieves the application, as the process can seem complicated, and is quite re-usable. The usage of this class is recommended.

  3. The DelegatingContainer optionally accepts a parent container.

    If your application is a module itself, and needs to be part of a larger application with its own DI container, supply it as the 2nd parameter. This will ensure that services will always be retrieved from the top-most container, regardless of where the definition is declared.

  4. The CachingContainer ensures services are cached.

    Effectively, this means that all services are singletons, i.e. there will only be one instance of each service in the application. This is most commonly the desired behaviour. Without the CachingContainer, i.e. with just the DelegatingContainer, service definitions will get invoked every time get() is called, which is usually undesirable.

  5. Conventions are important.

    If modules did not place the module.php file into their root directories, the bootstrap would not be able to load each module by just its package name. Modules which do not follow that convention must have their module.php file loaded separately, which would make the bootstrap code more complicated.

dhii/module-interface 适用场景与选型建议

dhii/module-interface 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 141.72k 次下载、GitHub Stars 达 6, 最近一次更新时间为 2017 年 06 月 19 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 dhii/module-interface 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 dhii/module-interface 我们能提供哪些服务?
定制开发 / 二次开发

基于 dhii/module-interface 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 141.72k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 6
  • 点击次数: 13
  • 依赖项目数: 5
  • 推荐数: 0

GitHub 信息

  • Stars: 6
  • Watchers: 4
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-06-19