1

Issues

So I try to decouple my application to multiple module project ( each has its own composer.json ), then the real application will load all this project through composer

Each of this module project will have a user-interface accessible through browser and can start individually, so it's not just a simple library. This file will exist on each module project:

  • config/application.config.php
  • public/index.php

Example Module ( Dependency is what I write in module array in application.config.php ):

  • UIModule
    • Dependency : AssetManager, UIModule
  • CMSModule
    • Dependency : UIModule, CMSModule
  • AccountingModule:
    • Dependency : UIModule, AccountingModule

Now in my final application lets say MyApplication it need both CMSModule and AccountingModule, but I cannot write only just this two module in application.config.php. Instead I have to write:

  • AssetManager -> this should be load by UIModule
  • UIModule -> this should be load by CMS/Accounting Module
  • CMSModule
  • AccountingModule

I should only require to write this two in MyApplication

  • CMSModule
  • AccountingModule

Is this can be done ? which I think what this guy want to achieve in Loading Modules Dynamically in Zend Framework 2

Something like this, I add another additional module. decouple module

wpsd
  • 303
  • 3
  • 21
  • You reference a question, have you read [this answer](https://stackoverflow.com/a/45309807/1155833) to that question as well? What you're trying is use the config (all config merged into 1 array) for 1 application as if it's more than 1 application. My advice based on your question: create 4 applications that have overlapping dependencies (such as the UI module and Authentication module) – rkeet Mar 13 '18 at 09:45
  • it's more like recursively include a module like composer. But maybe it's better to check if module is not included, then throw some exception – wpsd Mar 13 '18 at 13:09
  • My advice for you to build more than 1 application stands. Your comment really makes it sound more like you need 3 (?) applications but cant be bothered making more than 1. If you have Apps 1, 2 and 3, what's stopping you from having all 3 use modules A, B and C. Then have App 2 also use module D and App 3 use module E. It's really more maintainable to share vendor modules between applications than it is to share applications between modules. – rkeet Mar 13 '18 at 16:15
  • yes that's what i want to achieve, each module will be split to each vendor so I can split it to different developer and they will not easily break other package on change. But If the module is required to have UI (view) such as form and other design not just a service library, then one have to write "application" for each module. Then the idea came, lets have one global application module that will be use by other module as view so the layout will always be the same. – wpsd Mar 15 '18 at 13:49

2 Answers2

2

Based on our exchange in the comments and the question, you're going to need at least 3 applications. I'll give you a quick examples, you'll have to update your requirements for each application yourself. After the composer.json configs I'll give you a skeleton module to use as a theme module.

These config's are to be used as the root composer.json config files. Each of the required packages should have their own composer file listing requirements for the specific package.

For example, a "core" module would require various Zend Framework packages. A "theme" package could be requiring other ZF packages, such as zendframework/zend-view in order to be able to have a GUI layout.


Setting up 3 separate Zend Framework applications with overlapping requirements

composer.json for application 1

{
  "name": "COMPANY_NAME/APPLICATION_1",
  "require": {
    "COMPANY_NAME/MODULE_1_THEME": "*",
    "COMPANY_NAME/MODULE_2_CMS": "*"
  },
  "repositories": [
    {
      "type": "git",
      "url": "git@github.com/COMPANY_NAME/MODULE_1_THEME.git"
    },
    {
      "type": "git",
      "url": "git@github.com/COMPANY_NAME/MODULE_2_CMS.git"
    },
  ]
}

composer.json for application 2

{
  "name": "COMPANY_NAME/APPLICATION_2",
  "require": {
    "COMPANY_NAME/MODULE_1_THEME": "*",
    "COMPANY_NAME/MODULE_3_ACCOUNTING": "*"
  },
  "repositories": [
    {
      "type": "git",
      "url": "git@github.com/COMPANY_NAME/MODULE_1_THEME.git"
    },
    {
      "type": "git",
      "url": "git@github.com/COMPANY_NAME/MODULE_3_ACCOUNTING.git"
    },
  ]
}

composer.json for application 3 (has no theme)

{
  "name": "COMPANY_NAME/APPLICATION_3",
  "require": {
    "COMPANY_NAME/MODULE_4_AUTH_MODULE": "*"
  },
  "repositories": [
    {
      "type": "git",
      "url": "git@github.com/COMPANY_NAME/MODULE_4_AUTH_MODULE.git"
    }
  ]
}

As you can see, the Applications 1 & 2 use the same MODULE_THEME package, as you outlined in the diagram in your question.

Now, the creation of a package for Zend Framework is pretty much the same for every package you create, so modify what follows to the requirements you have for each module (in a package).

Creating a theme module

This module basically replaces the Application module that you get by default when you install the Zend Framework (2 or 3) Skeleton Application.

I've recently upgraded everything I have with Zend Framework to Zend Framework 3, so I'll be giving you a setup tailored for ZF3. However, downgrading for ZF2 should not be too much of an issue.

Create config for what you need

A typical theme needs a few things, such as:

  • themes/layouts for different types of pages (e.g. login, normal theme, errors)
  • translations
  • showing errors (when in dev mode)
  • default "home" route
  • controller to handle default "home" route

Config for this could be (not limited to! Do with it what you wish!) as such in the module.config.php of the Theme module

namespace COMPANY_NAME\Theme;

use COMPANY_NAME\Theme\Controller\ThemeController;
use COMPANY_NAME\Theme\Factory\ThemeControllerFactory;

return [
    'controllers' => [
        'factories' => [
            ThemeController::class => ThemeControllerFactory::class,
        ],
    ],
    'router' => [
        'routes' => [
            'home' => [
                'type' => Literal::class,
                'may_terminate' => true,
                'options' => [
                    'route'    => '/',
                    'defaults' => [
                        'controller' => ThemeController::class,
                        'action'     => 'index',
                    ],
                ],
            ],
        ],
    ],
    'route_layouts' => [
        '*'         => 'layout/layout',
        'login'     => 'layout/login',
        'register'  => 'layout/login',
        'error*'    => 'error/index',
        'error/404' => 'error/404',
    ],
    'translator' => [
        'locale' => 'en_US',
        'translation_file_patterns' => [
            [
                'type'     => 'gettext',
                'base_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'language',
                'pattern'  => '%s.mo',
            ],
        ],
    ],
    'view_manager' => [
        // controller_map is optional, but depending on your composer package nesting, could be a great help. Have a look here for how to use: https://blog.alejandrocelaya.com/2015/08/14/working-with-sub-namespaced-modules-in-zend-framework-2-the-right-way/
        'controller_map' => [
            'COMPANY_NAME\Theme' => 'company_name_path_alias',
        ],
        'display_not_found_reason' => true,
        'display_exceptions'       => true,
        'doctype'                  => 'HTML5',
        'not_found_template'       => 'error/404',
        'exception_template'       => 'error/index',
        'template_map' => [
            'layout/layout' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
                'layout' . DIRECTORY_SEPARATOR . 'layout.phtml',
            'layout/login' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
                'layout' . DIRECTORY_SEPARATOR . 'login.phtml',
            'error/404'               => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
                'error' . DIRECTORY_SEPARATOR . '404.phtml',
            'error/index'             => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
                'error' . DIRECTORY_SEPARATOR . 'index.phtml',
        ],
        'template_path_stack' => [
            __DIR__ . DIRECTORY_SEPARATOR .'..' . DIRECTORY_SEPARATOR . 'view',
        ],
    ],
];

File/module structure based on config

The location of the package would be /vendor/COMPANY_NAME/THEME_MODULE_NAME (as you would've defined in the name property in the composer.json file for this package.

The folder/file structure would be:

  • /vendor/COMPANY_NAME/THEME_MODULE_NAME
    • config/
      • module.config.php
    • src/
      • Controller/
        • ThemeController.php
      • Factory/
        • ThemeControllerFactory.php
      • Module.php
    • view/
      • error/
        • index.phtml
        • 404.phtml
      • layout/
        • index.phtml
        • login.phtml
        • register.phtml
    • composer.json

ThemeController & *Factory

These are very simple as the Controller is pretty much a clone of the original IndexController provided by the Skeleton Application. The Factory in this instance does nothing but return the Controller. As such you could replace the config for it with the FQCN to the InvokableFactory of Zend Framework 3 and not make the Factory class. However, if your ThemeController needs some requirements (such as a RegisterForm), you're going to need the Factory to provide these.

ThemeController

namespace COMPANY_NAME\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class ThemeController extends AbstractActionController
{
    public function indexAction()
    {
        return [];
    }
}

ThemeControllerFactory

namespace COMPANY_NAME\Factory;

use COMPANY_NAME\Controller\ThemeController;
use Zend\ServiceManager\Factory\FactoryInterface;

class ThemeControllerFactory implements FactoryInterface
{
    /**
     * @param ContainerInterface $container
     * @param string $requestedName
     * @param array|null $options
     * @return ThemeController
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return new ThemeController();
    }
}

Theme composer requirements

Obviously your modules will not have the same requirements. Make sure you figure out what they are, per module.

For my own Theme module, I have the following Zend Framework requirements in my composer.json file:

{
    "name": "COMPANY_NAME/THEME_MODULE_NAME",
    "require": {
        "zendframework/zend-di": "*",
        "zendframework/zend-navigation": "*",
        "zendframework/zend-view": "*",
    }
}

In the require section I also have: "rwoverdijk/assetmanager": "^1.6",. This module is used to mash together all CSS, JS (any type really) of file to a determined location. I would advise you to have a look at it (here).


Notes on the answer

  • Replace COMPANY_NAME with the username of your Github account (or the identifying account name if your using Bitbucket or Gitlab)
  • Replace THEME_MODULE_NAME with the name of the repository
  • If/when possible, use explicit versions for required packages (e.g. "rwoverdijk/assetmanager": "^1.6"). Version locking can save you a lot of hassle in the future...

Additionally: using a package as a "Theme module" allows you to completely remove the module/ folder originally shipped with the Skeleton Application of Zend Framework. However, you're hereby advised to use the module/ folder for application specific modules. If you create a package for everything, you'll soon find yourself maintenance hell.

rkeet
  • 3,406
  • 2
  • 23
  • 49
  • If/when possible, use explicit versions for required packages (e.g. "rwoverdijk/assetmanager": "^1.6"). Version locking can save you a lot of hassle in the future... Does this work with zf3, I try 1.7.3 it doesn't work throw some exception. Thats why I go back to zf2 for now – wpsd Mar 17 '18 at 22:51
  • Yes, versions locking always works. In the case of being in development, you might want to use `"*"` as the version (unlimited version), so you get the most recent updates included. Then, obviously, before you put it in production, you compare your `composer.lock` file and add the versions of the installed vendor packages to your `composer.json` files. That way, at the time to go into production, you have everything up to date. However, in the case of major updates this *could* give development delays when a vendor supplies such a major update ;) So always take care. – rkeet Mar 18 '18 at 09:19
0

Yep your layout is what I came at the end

However, you're hereby advised to use the module/ folder for application specific modules.

Kind of, I end up putting inside a folder for every specific package ( zf2 style )

  • PACKAGE FOLDER
    • composer.json
    • Module.php (this for php unit test)
    • public (for UI Package I have this)
      • index.php
    • config
      • application.config.php (apparently need to write each version for each package)
    • tests
    • src
      • MODULE_NAME
        • asset
        • src
          • MODULE_NAME
            • Controller
            • Service
            • Model
            • { Other ... }
        • config
        • view
        • Module.php

Thanks for your clarification and answer.

wpsd
  • 303
  • 3
  • 21