Как создать монолитный пакет Composer со встроенным плагином-компоновщиком?

Я хочу, чтобы мой пакет поставлялся со встроенным плагином-компоновщиком.

У меня есть такая структура:

composer.json
src/
    ...
plugin/
    composer.json
    src/
        ...

Корень composer.json настроен следующим образом:

{
    "name": "foo/bar",
    "type": "library",
    "autoload": {
        "psr-4": {
            "Foo\\Bar\\": "src/"
        }
    },
    "repositories": [
        {
            "type": "path",
            "url": "./tools",
            "options": {
                "symlink": false
            }
        }
    ],
    "require": {
        "foo/bar-plugin": "*"
    }
}

И встроенный композитор-плагин plugin/composer.json выглядит следующим образом:

{
    "name": "foo/bar-plugin",
    "type": "composer-plugin",
    "require": {
        "composer-plugin-api": "^1",
        "composer/composer": "^1",
        "foo/bar": "*"
    },
    "autoload": {
        "psr-4": {
            "Foo\\Bar\\Plugin\\": "src/"
        }
    },
    "extra": {
        "class": "Foo\\Bar\\Plugin\\MyComposerPlugin"
    }
}

Обратите внимание, как здесь существует двухсторонняя зависимость - плагин зависит от foo/bar, а сам проект зависит от foo/bar-plugin.

Здесь, где это становится странным. Во время свежей установки, например, composer install или composer update, все в порядке - плагин делает это, что прямо сейчас означает просто объявить себя на консоли.

Теперь, после установки, если я набираю только composer, я ожидаю, что сам анонс плагина, как и прежде, будет правильным?

Вместо этого он генерирует фатальную "не найденную ошибку класса", как только он пытается ссылаться на любой класс, относящийся к пакету foo/bar.

Как будто композитор проигнорировал тот факт, что foo/bar-plugin требует foo/bar, и по какой-то причине он не автозагружаемый.

Есть ли причина, по которой это невозможно? Почему бы и нет?

Конечно, я могу просто упаковать этот материал в отдельный внешний пакет, но это не будет иметь большого смысла, поскольку эти пакеты просто будут зависеть друг от друга - они фактически являются одной единицей, упаковывая их как два пакета приведут к беспорядку основной версии с каждым изменением, так как в основном каждая релиз foo/bar сломается foo/bar-plugin.

В идеале я хотел бы просто добавить композитор-плагин непосредственно в основной пакет, но кажется, что по какой-то причине это невозможно? Кажется, что только пакет с типом composer-plugin позволяет добавлять плагины?

Ответы

Ответ 1

Если плагин по существу является частью вашего пакета, вы не должны использовать его как таковой. Композитор предлагает альтернативы.

Как указал Йенс в комментарии к вашему вопросу, в composer.json есть ключ "скриптов". Вы можете вызвать команды оболочки внутри, но также вызвать методы статического класса.

О плагиновском решении - композитор явно упоминает об этом на своем сайте:

Composer не делает предположений о состоянии ваших зависимостей перед установкой или обновлением. Поэтому вам не следует указывать сценарии, требующие зависимостей, управляемых с помощью Composer, в крючках событий pre-update-cmd или pre-install-cmd. Если вам нужно выполнить скрипты перед установкой или обновлением, убедитесь, что они автономны в корневом пакете.

(моя боковая заметка - это также примерно применимо к плагинам).

В любом случае - предоставить вам решение: отказаться от подхода "плагин". Вместо этого измените свой файл composer.json, чтобы он выглядел следующим образом:

composer.json

{
    "name": "foo/bar",
    "type": "library",
    "autoload": {
        "psr-4": {
            "Foo\\Bar\\": "src/"
        }
    },
    "require": {
    },

    "scripts": {
        "post-install-cmd": [
            "Foo\\Bar\\Composer\\Plugin::postInstall"
        ],
        "post-update-cmd": [
            "Foo\\Bar\\Composer\\Plugin::postUpdate"
        ]        
    }

}

Кроме того, в папке src/Composer создайте Plugin.php:

ЦСИ/Композитор/plugin.php

<?php

namespace Foo\Bar\Composer;

use Foo\Bar\Test;

/**
 * Composer scripts.
 */
class Plugin
{
    public static function postInstall()
    {
        print_r("POST INSTALL\n");
        print_r(Test::TEST_CONST);
        print_r("\n");
    }

    public static function postUpdate()
    {
        print_r("POST UPDATE\n");
        print_r(Test::TEST_CONST);
        print_r("\n");
    }
}

Как вы видите, он печатает константу из класса Test. Создайте его в src/:

ЦСИ /test.php

<?php

namespace Foo\Bar;

/**
 * Test class.
 */
class Test
{
    const TEST_CONST = "HERE I AM";
}

Запустите это и проверьте, как он проигрывается.