Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop module manager and add support for config aggregator #56

Merged
merged 13 commits into from
Jun 3, 2022

Conversation

Xerkus
Copy link
Member

@Xerkus Xerkus commented Jun 9, 2020

This PR introduces changes necessary to cut out module manager from MVC application.

Usage originally envisioned for module manager is no longer relevant:

  • Package specific autoloader handling is superceded by Composer
  • Shared application installations are a thing of the past. Applications are no longer expected to change feature sets at runtime.
  • A need for a flexible programmatic application assembly is superseded by dependency injection containers
  • Components configuration is immediately available thanks to how composer autoloading works. There is no need to load modules anymore before configuration providers can be gathered and config merged.
  • Frontend assets management or package dependencies resolution are no longer needed. Etc.
    -Most used remaining features are a config provider and onBootstrap() convenience listener.

As proven by Mezzio, much simpler laminas-config-aggregator coupled with Container is more than sufficient.

Changes:

  • Module Manager removed along with its dependnecies
  • Laminas\Mvc\Application::init() is removed in favor of Laminas\Mvc\ConfigProvider for laminas-config-aggregator
  • Laminas\Mvc\Service\ConfigFactory removed as config can now be merged and provided to Container before Container is created
  • List of extra Application listeners moved from separate application config into main config under the $config[\Laminas\Mvc\Application::class]['listeners'] key
  • Configuration for the various PluginManagers moved from removed Laminas\ModuleManager\Listener\ServiceListener into each respective plugin manager factory
  • ServiceManagerConfig removed and replaced by ConfigProvider with the goal to eventually remove hard dependency on laminas-servicemanager as a main container.
  • ModuleManager's onBootstrap() convenience listener is not replaced by any alternative at this time.

Documentation tracking issue: #58

@Xerkus Xerkus force-pushed the feature/drop-modulemanager-dependency branch from 02f72d5 to b53dfe8 Compare June 12, 2020 11:59
@Xerkus Xerkus linked an issue Jun 12, 2020 that may be closed by this pull request
9 tasks
@Xerkus Xerkus added this to the 4.0.0 milestone Jun 12, 2020
@Xerkus Xerkus force-pushed the feature/drop-modulemanager-dependency branch from b53dfe8 to 86bcde3 Compare June 12, 2020 17:07
@Xerkus Xerkus marked this pull request as ready for review June 12, 2020 17:11
@Xerkus
Copy link
Member Author

Xerkus commented Jun 12, 2020

This PR does not concern itself with code quality and intentionally avoids any changes that are not a bare necessity

Xerkus added a commit to Xerkus/laminas-mvc that referenced this pull request Jun 12, 2020
Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think it's a nice step!

I think the one thing missing for me is a write-up in the patch description detailing:

  • your proposal for where the config and/or container are built
    • AND what each of those files look like
  • what the public/index.php looks like

As somebody familiar with Mezzio (clearly!) those details seem obvious, but for somebody not familiar with them, having that information documented here will help them understand how we are evolving the MVC, and what it will look like once complete.

Comment on lines -138 to -139
public function bootstrap(array $listeners = [])
public function bootstrap()
{
$serviceManager = $this->serviceManager;
$events = $this->events;

// Setup default listeners
$listeners = array_unique(array_merge($this->defaultListeners, $listeners));

foreach ($listeners as $listener) {
$serviceManager->get($listener)->attach($events);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you removing the ability to pass additional listeners to bootstrap()?

To be clear, I'm fine with it (I don't think we document it anywhere), but you'll need to detail it as a feature removal in the changelog as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra listeners are moved to the application factory since Application::init() is going to be dropped.

real change here is that extra listeners to register would migrate from the application.config.php to the merged config

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines -233 to -265
/**
* Static method for quick and easy initialization of the Application.
*
* If you use this init() method, you cannot specify a service with the
* name of 'ApplicationConfig' in your service manager config. This name is
* reserved to hold the array from application.config.php.
*
* The following services can only be overridden from application.config.php:
*
* - ModuleManager
* - SharedEventManager
* - EventManager & Laminas\EventManager\EventManagerInterface
*
* All other services are configured after module loading, thus can be
* overridden by modules.
*
* @param array $configuration
* @return Application
*/
public static function init($configuration = [])
{
// Prepare the service manager
$smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : [];
$smConfig = new Service\ServiceManagerConfig($smConfig);

$serviceManager = new ServiceManager();
$smConfig->configureServiceManager($serviceManager);
$serviceManager->setService('ApplicationConfig', $configuration);

// Load modules
$serviceManager->get('ModuleManager')->loadModules();

// Prepare list of listeners to bootstrap
$listenersFromAppConfig = isset($configuration['listeners']) ? $configuration['listeners'] : [];
$config = $serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];

$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));

return $serviceManager->get('Application')->bootstrap($listeners);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you removing this method?

Removal of this MUST be called out in both the CHANGELOG and the documentation, as our own skeleton has consistently used this method in order to initialize and bootstrap the application from aggregated configuration. If it goes away, users need to know what they need to change in their public/index.php.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method no longer provides utility it once did.
With the the module manager dropped, responsibility to setup the application pushed completely out of the scope of this package.

It is now entirely up to skeleton application to provide the default wiring. As initially outlined in the Zend\Mvc v4 RFC the goal is to move towards current mezzio skeleton application setup. Config+container proved to be flexible enough.

At this time I am working on a new feature that would somewhat approximate laminas-httphanderrunner and would affect how public/index.php needs to be changed.

On a side note, I have no immediate intention to provide update for the laminas-modulemanager. I can easily see it retired unless there is a demand for the different set of features not available with the config aggregator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +110 to +128
'EventManagerAwareInitializer' => static function ($first, $second) {
if ($first instanceof ContainerInterface) {
$container = $first;
$instance = $second;
} else {
$container = $second;
$instance = $first;
}

if (! $instance instanceof EventManagerAwareInterface) {
return;
}

$eventManager = $instance->getEventManager();

// If the instance has an EM WITH an SEM composed, do nothing.
if ($eventManager instanceof EventManagerInterface
&& $eventManager->getSharedManager() instanceof SharedEventManagerInterface
) {
return;
}

$instance->setEventManager($container->get('EventManager'));
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify this by only allowing laminas-servicemanager v3+ and laminas-eventmanager v3+. Personally, I'd do that as part of this patch to simplify this part of the ConfigProvider.

Alternately, move that code into a dedicated method, and use Closure::fromCallable() to provide it as an initializer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change would be outside of the scope for the module manager removal.

I do not see a reason to keep EventManager initializer and expect to drop any initializers entirely as a part of the move towards generic PSR containers. I will need to double check container integrations supported by mezzio, I am not sure if it is something supported by all them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

$listeners = $container->get('config')[Application::class]['listeners'] ?? [];
foreach ($listeners as $listener) {
$container->get($listener)->attach($em);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This answers part of my question about the changes to Application::bootstrap(). However, you need to document this in the changelog and the manual.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant more of a placeholder at this point that is meant to preserve the functionality. Event system would need to have a more detailed revision and potential utilization of PSR-14 would change this part entirely.

One of the reasons why I opted for skipping documenting the changes in CHANGELOG and providing updated documentation by this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
// Configure URL view helper
$urlFactory = $this->createUrlHelperFactory($services);
$plugins->setFactory(ViewHelper\Url::class, $urlFactory);
$plugins->setFactory('laminasviewhelperurl', $urlFactory);
$managerConfig['aliases']['laminasviewhelperurl'] = ViewHelper\Url::class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop these "normalized" aliases - users should only be using the FCQN or the class-name-minus-namespace when retrieving helpers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That change would be outside of the scope of this PR. Too easy to lose track of small details like that during review.

I plan to drop all obsolete aliases in another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@geerteltink geerteltink changed the base branch from next to 4.0.x September 12, 2020 09:02
@Xerkus Xerkus changed the base branch from backup-4.0.x-at-2022-02-24 to 4.0.x June 3, 2022 16:15
@Xerkus Xerkus force-pushed the feature/drop-modulemanager-dependency branch from e4e6b6c to 0f36590 Compare June 3, 2022 16:15
Application listeners should be registered by the time `Application`
service is retrieved from container. Previously they were registered at
the time of `Application::bootstrap()` call.

Signed-off-by: Aleksei <[email protected]>
@Xerkus
Copy link
Member Author

Xerkus commented Jun 3, 2022

Migration path for existing users would need to be provided via separate package providing necessary bindings between module manager and mvc.
config() and onBootstrap() are two most often used module methods. First one is covered by config provider while second... I would need to look at event changes first. I do not remember intended details anymore, but it involved PSR event listener providers.

Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good.

The only feedback I have is around usage of the laminas-servicemanager FactoryInterface; I think we could drop explicitly implementing that interface, and just doing invokables, to help reduce ties to laminas-servicemanager. However, that change is likely out of scope for this PR.

Comment on lines -138 to -139
public function bootstrap(array $listeners = [])
public function bootstrap()
{
$serviceManager = $this->serviceManager;
$events = $this->events;

// Setup default listeners
$listeners = array_unique(array_merge($this->defaultListeners, $listeners));

foreach ($listeners as $listener) {
$serviceManager->get($listener)->attach($events);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines -233 to -265
/**
* Static method for quick and easy initialization of the Application.
*
* If you use this init() method, you cannot specify a service with the
* name of 'ApplicationConfig' in your service manager config. This name is
* reserved to hold the array from application.config.php.
*
* The following services can only be overridden from application.config.php:
*
* - ModuleManager
* - SharedEventManager
* - EventManager & Laminas\EventManager\EventManagerInterface
*
* All other services are configured after module loading, thus can be
* overridden by modules.
*
* @param array $configuration
* @return Application
*/
public static function init($configuration = [])
{
// Prepare the service manager
$smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : [];
$smConfig = new Service\ServiceManagerConfig($smConfig);

$serviceManager = new ServiceManager();
$smConfig->configureServiceManager($serviceManager);
$serviceManager->setService('ApplicationConfig', $configuration);

// Load modules
$serviceManager->get('ModuleManager')->loadModules();

// Prepare list of listeners to bootstrap
$listenersFromAppConfig = isset($configuration['listeners']) ? $configuration['listeners'] : [];
$config = $serviceManager->get('config');
$listenersFromConfigService = isset($config['listeners']) ? $config['listeners'] : [];

$listeners = array_unique(array_merge($listenersFromConfigService, $listenersFromAppConfig));

return $serviceManager->get('Application')->bootstrap($listeners);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +48 to +49
'request' => 'Request',
'response' => 'Response',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the request and response should be services anymore in v4, but I'm unsure if that's in-scope for this patch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They definitely do not belong to services.
I plan to drop them not only from container but also from bootstrap event. New event would need to be introduced to do request aware bootstap before routing. I think I did that work in zend-mvc but abandoned when we migrated to laminas.

We discussed it long time ago, and it should be on the old rfc: it would make sense to inject them to the run method with laminas-httphandlerrunner when mvc is migrated to PSR and something similar before that.

Comment on lines +110 to +128
'EventManagerAwareInitializer' => static function ($first, $second) {
if ($first instanceof ContainerInterface) {
$container = $first;
$instance = $second;
} else {
$container = $second;
$instance = $first;
}

if (! $instance instanceof EventManagerAwareInterface) {
return;
}

$eventManager = $instance->getEventManager();

// If the instance has an EM WITH an SEM composed, do nothing.
if ($eventManager instanceof EventManagerInterface
&& $eventManager->getSharedManager() instanceof SharedEventManagerInterface
) {
return;
}

$instance->setEventManager($container->get('EventManager'));
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

$listeners = $container->get('config')[Application::class]['listeners'] ?? [];
foreach ($listeners as $listener) {
$container->get($listener)->attach($em);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

use Laminas\View\Helper as ViewHelper;
use Laminas\View\HelperPluginManager;

class ViewHelperManagerFactory extends AbstractPluginManagerFactory
class ViewHelperManagerFactory implements FactoryInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I'd drop the implements FactoryInterface part. Factories only need to be invokable,and it's another explicit binding to laminas-servicemanager instead of a generic PSR-11 implementation.

Copy link
Member Author

@Xerkus Xerkus Jun 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

Factories look for extra input parameters that are only supported by service manager. the $options specifically, while for the $name we do provide decorators for others containers iirc.

I will have a pass dedicated to factories. I think quite a few of them can be safely dropped and replaced by factories provided by components.

{
// Configure URL view helper
$urlFactory = $this->createUrlHelperFactory($services);
$plugins->setFactory(ViewHelper\Url::class, $urlFactory);
$plugins->setFactory('laminasviewhelperurl', $urlFactory);
$managerConfig['aliases']['laminasviewhelperurl'] = ViewHelper\Url::class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@manishranjan-adobe
Copy link

Hi @froschdesign When do you plan to release version 4.0.*?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[WIP]Drop module manager
3 participants