Reinit composer自动加载


Reinit composer autoload

通过Composer更新后,我想初始化应用程序并将事件发送给它

"scripts": {
    "post-update-cmd": [
        "Acme''Bundle''DemoBundle''Composer''ScriptHandler::notify"

处理机

public static function notify(CommandEvent $event)
{
    // init app
    require __DIR__.'/../../../../../../app/autoload.php';
    require __DIR__.'/../../../../../../app/AppKernel.php';
    $kernel = new 'AppKernel('dev', true);
    $kernel->boot();
    // send event
    $dispatcher = $kernel->getContainer()->get('event_dispatcher');
    $dispatcher->dispatch('acme.installed', new Event())
}

如果通过composer.phar运行更新,则一切正常。

但我需要从应用程序运行更新。我将composer添加到需求中,并调用bin'composer update

在这种情况下,存在自动加载器冲突。Composer从应用程序连接自动加载器,对其进行更改,然后不再连接。

需要销毁旧的并创建一个新的自动加载器。我发现旧的自动加载器可以通过$GLOBALS['loader']访问。

我做出这个决定

public static function notify(CommandEvent $event)
{
    // init loader
    require __DIR__.'/../../../../../../vendor/composer/autoload_real.php';
    $GLOBALS['loader']->unregister();
    $GLOBALS['loader'] = require __DIR__.'/../../../../../../app/autoload.php';
    // init app
    require_once __DIR__.'/../../../../../../app/AppKernel.php';
    // ...

但此选项不起作用,因为在正常的require中通过文件广播Composer自动加载会导致连接冲突。

例如:

"name": "kriswallsmith/assetic",
"autoload": {
    "files": [ "src/functions.php" ]
},

转换为

require $vendorDir . '/kriswallsmith/assetic/src/functions.php';

抛出错误

PHP Fatal error:  Cannot redeclare assetic_init() (previously declared in /vendor/kriswallsmith/assetic/src/functions.php:20) in /vendor/kriswallsmith/assetic/src/functions.php on line 26

我创建了自动加载器,并复制了/vendor/composer/autoload_real.php/app/autoload.php的代码。来自Seldaek#2474 的建议

public static function notify(CommandEvent $event)
{
    if (isset($GLOBALS['loader']) && $GLOBALS['loader'] instanceof ClassLoader) {
        $GLOBALS['loader']->unregister();
    }
    $GLOBALS['loader'] = $this->getClassLoader();
    require_once __DIR__.'/../../../../../../app/AppKernel.php';
    $kernel = new 'AppKernel('dev', true);
    $kernel->boot();
    // send event
    $dispatcher = $kernel->getContainer()->get('event_dispatcher');
    $dispatcher->dispatch('acme.installed', new Event())
}
protected function getClassLoader()
{
    $loader = new ClassLoader();
    $vendorDir = __DIR__.'/../../../../../../vendor';
    $baseDir = dirname($vendorDir);
    $map = require $vendorDir . '/composer/autoload_namespaces.php';
    foreach ($map as $namespace => $path) {
        $loader->set($namespace, $path);
    }
    $classMap = require $vendorDir . '/composer/autoload_classmap.php';
    if ($classMap) {
        $loader->addClassMap($classMap);
    }
    $loader->register(true);
    $includeFiles = require $vendorDir . '/composer/autoload_files.php';
    foreach ($includeFiles as $file) {
        require_once $file;
    }
    // intl
    if (!function_exists('intl_get_error_code')) {
        require_once $vendorDir.'/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs/functions.php';
        $loader->add('', $vendorDir.'/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs');
    }
    AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
    return $loader;
}

我想我有点害怕你要做的事情:你想在活跃使用这些组件的同时更新正在运行的应用程序的组件。您希望任何更新都能运行良好,以便在更新后应用程序能够继续工作,尤其是能够继续进行进一步的更新。

我认为这不是一个有效的假设!我使用Composer已经有一段时间了,我已经看到了很多原因,为什么它没有更新我的依赖项的某些部分,大多数时候是由于一些网络故障。只需考虑使用Github中的一些东西,然后Github就坏了。

那会发生什么?您可能能够下载一些部件,但无法下载更多部件,并且自动加载器没有更新。因此,更新后的部分现在需要一些新的内容,这些内容也已下载,但由于之后的某些组件无法下载,因此无法自动加载。这个组件对于重复更新是至关重要的!你刚刚破坏了你的应用程序,你无法修复它。

我还可以想象,如果自动加载器部分加载旧类,然后进行更新,然后加载新类,这些新类使用已加载的、不兼容的旧类的新版本,会产生非常奇怪的效果。因此,假设您可以在一个请求的运行时更改应用程序的组件似乎非常奇怪。

PHP中没有卸载类的方法。一旦声明了它,就不能更改它(不考虑"runkit"扩展)。

如果你确实想更新你的应用程序,我认为最好复制所有内容,更新应用程序的非活动副本,然后检查更新是否成功,然后将其复制回来,即使用符号链接指向当前和下一个版本并切换它们。