Application initializers #2775

Closed
alpharder opened this Issue Mar 17, 2014 · 7 comments

Comments

Projects
None yet
4 participants
@alpharder

For now it's only possible to run code when application is initializing with extension bootstrap classes.

The problem: I need to run code on application init part without using extended Application class.

The possible solution: use Application extensions property, like this:

// configuration file
return [
    'extensions' => array_merge(
        require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
        [
             ['bootstrap'=>'path\to\Class']
        ]
    )
];

This solution isn't clear and may cause different side-effects when managing extensions.

The perfect solution: application initializer classes. They should be specified at application configuration, like this:

return [
    'initializers' => [
        'path\to\Class',
        ...
    ]
]; 

In Laravel, they're called Service providers.

yii\base\Application::initExtensions() will be able to add extensions' bootstrap classes to initializers property and allow new method yii\base\Application::runInitializers() to call their init() method.

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Mar 17, 2014

Contributor

in L4 service providers are for DI, no ? Anyway i see this like initializers in rails right ? Could be useful.

Contributor

Ragazzo commented Mar 17, 2014

in L4 service providers are for DI, no ? Anyway i see this like initializers in rails right ? Could be useful.

@alpharder

This comment has been minimized.

Show comment
Hide comment
@alpharder

alpharder Mar 17, 2014

@Ragazzo L4? of course not only for DI, there are use-cases different from IoC registrations.

The concept of initializers can be useful for any purposes.
For example, in a CMS for my projects there are a lot of base modules (user, mail, etc.).
Any project-specific functionality is merged into a separate module.

Each module has it's own configuration file, which is automatically required and merged with application config before application runs. For example, socialnetworks module needs to add additional login method class to user module, so it's configuration file looks like:

<?php
return [
  'modules'=>[
    'user'=>[
      'loginMethods'=>['social'=>'path\to\LoginMethodClass']
    ],
    'socialNetworks'=>'path\To\ModuleClass'
  ]
];

So resulting config will be merged with this, and as a result "user" module will have additional "social" login method. This solution is a dirty walkaround (I have to generate config files, regenerate when they change, etc.), the cleaner solution is:

  • socialnetworks module's initializer class is added to app config
  • It registers additional login method for user module:
<?php
class SocialNetworksInitializer implements IAmInitializer
{
      public static function init(Application $app)
      {
          $app->getModule('user')->loginMethods['social'] = 'path\to\LoginMethodClass';
      }
}

@Ragazzo L4? of course not only for DI, there are use-cases different from IoC registrations.

The concept of initializers can be useful for any purposes.
For example, in a CMS for my projects there are a lot of base modules (user, mail, etc.).
Any project-specific functionality is merged into a separate module.

Each module has it's own configuration file, which is automatically required and merged with application config before application runs. For example, socialnetworks module needs to add additional login method class to user module, so it's configuration file looks like:

<?php
return [
  'modules'=>[
    'user'=>[
      'loginMethods'=>['social'=>'path\to\LoginMethodClass']
    ],
    'socialNetworks'=>'path\To\ModuleClass'
  ]
];

So resulting config will be merged with this, and as a result "user" module will have additional "social" login method. This solution is a dirty walkaround (I have to generate config files, regenerate when they change, etc.), the cleaner solution is:

  • socialnetworks module's initializer class is added to app config
  • It registers additional login method for user module:
<?php
class SocialNetworksInitializer implements IAmInitializer
{
      public static function init(Application $app)
      {
          $app->getModule('user')->loginMethods['social'] = 'path\to\LoginMethodClass';
      }
}
@alpharder

This comment has been minimized.

Show comment
Hide comment
@alpharder

alpharder Mar 17, 2014

I've just read about Rails initializer files. Yes, I propose similar concept.

I've just read about Rails initializer files. Yes, I propose similar concept.

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Mar 17, 2014

Contributor

Maybe we also should add two events for application BEFORE_INIT / AFTER_INIT ?

Contributor

Ragazzo commented Mar 17, 2014

Maybe we also should add two events for application BEFORE_INIT / AFTER_INIT ?

@alpharder

This comment has been minimized.

Show comment
Hide comment
@alpharder

alpharder Mar 17, 2014

I'm not sure about BEFORE_INIT - when to trigger it?

// Application constructor
public function __construct($config = [])
{
  Yii::$app = $this;
  // 1. Nothing have happened here.
  // On this line it's still possible to place some code 
  // that configures PHP like `ini_set()`

  $this->preInit($config); // initializes app paths and timezone
  $this->registerErrorHandlers();
  $this->registerCoreComponents();
  // Core components and paths are ready here.

  Component::__construct($config); // init() is being called here
  // extensions are initialized
  // controller namespace is ready
  // preloading components are loaded
}

I'm not sure about BEFORE_INIT - when to trigger it?

// Application constructor
public function __construct($config = [])
{
  Yii::$app = $this;
  // 1. Nothing have happened here.
  // On this line it's still possible to place some code 
  // that configures PHP like `ini_set()`

  $this->preInit($config); // initializes app paths and timezone
  $this->registerErrorHandlers();
  $this->registerCoreComponents();
  // Core components and paths are ready here.

  Component::__construct($config); // init() is being called here
  // extensions are initialized
  // controller namespace is ready
  // preloading components are loaded
}

@qiangxue qiangxue added this to the 2.0 Beta milestone Mar 17, 2014

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Mar 17, 2014

Member

I think we can add Application::bootstrap property which takes an array of class names. During init stage of application, the bootstrap method of each class will be called. I'm using bootstrap() instead of init() because the latter is already used in classes extending Object.

Member

qiangxue commented Mar 17, 2014

I think we can add Application::bootstrap property which takes an array of class names. During init stage of application, the bootstrap method of each class will be called. I'm using bootstrap() instead of init() because the latter is already used in classes extending Object.

@qiangxue qiangxue closed this in d2c8460 Mar 17, 2014

@alpharder

This comment has been minimized.

Show comment
Hide comment
@alpharder

alpharder Mar 17, 2014

Thanks, @qiangxue ! This is the step up

Thanks, @qiangxue ! This is the step up

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