paneric/module-resolver

Simple, yet still useful, static tool (meaning, based on configuration) for specifying module's folder name in case of modular project architecture, accompanied by settings collector.

v4.0.0 2022-09-28 10:24 UTC

README

Simple, yet still useful, tool for specifying module's folder name in case of modular project architecture, accompanied by dynamic (no config required) definitions collector.

Required

  • PHP 7.1+

File structure

  • ModuleResolver.php
  • DefinitionsCollector.php
  • module-resolver-config.php (an example of required configuration)

Installation

composer

composer require paneric/module-resolver

Integration

Slim Framework/PHP-DI example (ver.4)

Module Resolver

The main role of the Module Resolver is to establish configuration folders. Both for an application itself and a current module that is called. This task is completed based on current route pattern first element.

In case of project's modular structure, it is highly possible to relay on "double scoped" definitions (the first, common for an application itself and the second, that is specific for an application module or installed package).

In order to fully profit all the features delivered by this package, there is one specific condition that is required to be fulfilled. Each module/package has to be related to its proper routes patters first element.
These relations have to be written in config as an array under the module_map key.

Application Module Example

In case of code exists as an application module

  • application module folder: src/Book
  • application module routes patterns: /book/... , /books/... , /bk/... (one or more)

src/config/module-resolver-config.php

'module_map' => [
    'book' => ROOT_FOLDER . 'src/Book',
    'books' => ROOT_FOLDER . 'src/Book',
    'bk' => ROOT_FOLDER . 'src/Book',
    ...
],

Package Example

In case of code that is supposed to be used also as composer package

  • package folder: src/Book
  • package routes patterns: /book/... , /books/... , /bk/... (one or more)

src/config/module-resolver-config.php

'module_map_cross' => [
    'book' => ROOT_PACKAGE . 'src/Book',
    'books' => ROOT_PACKAGE . 'src/Book',
    'bk' => ROOT_PACKAGE . 'src/Book',
    ...
],

Configuration

Application only with modules

src/config/module-resolver-config.php

<?php

declare(strict_types=1);

return [
    'default_route_key' => 'lc',
    'local_map' => ['en', 'pl'],
    'dash_as_slash' => false,
    'merge_module_cross_map' => false,
    'module_map' => [
        'lc' => ROOT_FOLDER . 'src/ListCountryApp',
        'api-lc' => ROOT_FOLDER . 'src/ListCountryApi',
        'api-lcs' => ROOT_FOLDER . 'src/ListCountryApi',
        'apc-lc' => ROOT_FOLDER . 'src/ListCountryApc',
    ],
    'module_map_cross' => [
        'lc'       => ROOT_FOLDER . 'vendor/paneric/e-commerce-list-country/src/ListCountryApp',
        'api-lc'   => ROOT_FOLDER . 'vendor/paneric/e-commerce-list-country/src/ListCountryApi',
        'api-lcs'  => ROOT_FOLDER . 'vendor/paneric/e-commerce-list-country/src/ListCountryApi',
        'apc-lc'   => ROOT_FOLDER . 'vendor/paneric/e-commerce-list-country/src/ListCountryApc',
    ],
];

Application with modules and packages

src/config/module-resolver-config.php

<?php

declare(strict_types=1);

use Paneric\ModuleResolver\ModuleMapper;

$moduleMapper = new ModuleMapper();

$moduleMapCrossPaths = [
    'vendor/paneric/e-commerce-list-country', 
    'vendor/paneric/e-commerce-list-type-company', 

];

$moduleMapPath = 'vendor/paneric/e-commerce-address';

$moduleMap = [
    'adr'      => ROOT_FOLDER . '%s/src/AddressApp',
    'api-adr'  => ROOT_FOLDER . '%s/src/AddressApi',
    'api-adrs' => ROOT_FOLDER . '%s/src/AddressApi',
    'apc-adr'  => ROOT_FOLDER . '%s/src/AddressApc',
];

return [
    'default_route_key' => 'adr',
    'local_map' => ['en', 'pl'],
    'dash_as_slash' => false,
    'merge_module_cross_map' => true,    

    'module_map' => $moduleMapper->setModuleMap($moduleMap),

    'module_map_cross' => $moduleMapper->setModuleMapCross(
        $moduleMapCrossPaths,
        $moduleMap,
        $moduleMapPath,
        __DIR__ !== ROOT_FOLDER . 'src/config'
    ),
];

Configuration details

  • 'default_route_key' - default first element of a route pattern;
  • 'local_map' - an array of language version markers intended to be recognized within your routes;
  • 'dash_as_slash' - in case if you choose to use dashes instead of slashes as your route pattern elements separator;
  • 'merge_module_cross_map' - in case of application consists of both its own modules and composer packages;
  • 'module_map' - an array/mapper of application modules names related to the first segment of a route;
  • 'module_map_cross' - an array/mapper prepared for a case if code is to be used as the composer package of a complex structure;

ProTip: as a consequence, above example of configuration: 'home' => 'Website', will establish Website/ as a module folder name for following examples of routes: /home, /home/fr or /home/sth/fr .

Definitions Collector

front controller

Let's consider an example of some modular project structure as follows:

  • my_project
    • public
      • index.php
    • src
      • config
        • module-resolver-config.php
      • definitions
        • di
          • orm.php
          • twig.php
          • ...
        • settings
          • dev.php
          • prod.php
          • translation.en.php
          • ...
        • ...
      • Website
        • definitions
          • di
            • controller.php
            • repository.php
            • service.php
            • ...
          • settings
            • translation.en.php
            • ...
        • Entity
        • Controller
        • Repository
        • templates
        • ...
      • Registration
        • definitions
          • di
            • controller.php
            • middleware.php
            • package.php
            • ...
          • settings
            • validation.php
            • ...
        • ...
      • ...

In this case our practical integration of the module resolver may look this way:

use Paneric\ModuleResolver\ModuleResolver;
use Paneric\ModuleResolver\DefinitionsCollector;
use DI\ContainerBuilder;
use Slim\Factory\AppFactory;

define('ENV', 'dev');

define('ROOT_FOLDER', dirname(__DIR__) . '/');
define('APP_FOLDER', ROOT_FOLDER . 'src/');

require ROOT_FOLDER . 'vendor/autoload.php';

try{
    $moduleResolverConfig = require_once APP_FOLDER . 'config/module_resolver_config.php';
    $moduleResolver = new ModuleResolver();
    $moduleFolderName = $moduleResolver->setModuleFolderName(
        $_SERVER['REQUEST_URI'], 
        $moduleResolverConfig
    );

    $definitionsCollector = new DefinitionsCollector();
    $definitions = $definitionsCollector->setDefinitions(
        APP_FOLDER,
        APP_FOLDER . $moduleFolderName, 
        'definitions'
    );

    $builder = new ContainerBuilder();
    $builder->addDefinitions($definitions);
    $container = $builder->build();

    AppFactory::setContainer($container);
    $app = AppFactory::create();

    // ... (any other require) //

    $app->run();
} catch (Exception $e) {
   echo $e->getMessage();
}

ProTip: definitions** states for folder name of php-di definitions. Only its sub-folders (1 st level exclusively) are scanned for .php definitions files (returning array).

If you need some environment variables to include in your settings file, it has to be named according to your environment variable value (dev or prod) set in the front controller. The env value is passed to the definitions collector method as the last parameter (optional, default value is set to null).

It works the the same in case of translation settings related to "local" method parameter (also optional, default parameter value is set to null) under one condition however. Your translation settings file name has to follow a particular convention. Regardles of the location (application settings folder or module settings folder) it has to be for example "translation.en.php" where "en" is actual local value for "English". If you set translation files both in src/definitions/settings and src/Module/definitions/settings remember to merge appropriate arrays during translator instantiation.

To make a long story short it will be necessary to modify settings collector "setSettings" method call as:

    $settingsCollector = new SettingsCollector();
    $settings = $settingsCollector->setSettings(
        APP_FOLDER,
        APP_FOLDER . $moduleFolderName, 
        'definitions',
        $local
    );

if you need only some translation dictionary, or:

    $settingsCollector = new SettingsCollector();
    $settings = $settingsCollector->setSettings(
        APP_FOLDER,
        APP_FOLDER . $moduleFolderName, 
        'definitions',
        $local, 
        ENV
    );

if you need some translation dictionary along with some environmental settings, or:

    $settingsCollector = new SettingsCollector();
    $settings = $settingsCollector->setSettings(
        APP_FOLDER,
        APP_FOLDER . $moduleFolderName, 
        'definitions',
        '', 
        ENV
    );

if you need only some environmental settings.

ProTip: "... (any other require)" mentioned in the front controller code related to separated dependencies, routes etc can be realized by the following manner:

     // twig, orm
    require_once APP_FOLDER . 'definitions/dependencies.php';

    // controllers, repositories, middlewares, extensions, etc;
    require_once APP_FOLDER . $moduleFolderName . 'definitions/dependencies.php';

    // and of course routes
    require_once APP_FOLDER . $moduleFolderName . 'definitions/routes.php';