├── src
├── Provider
│ ├── ProviderInterface.php
│ ├── AutoloadDependentProviderInterface.php
│ ├── TolerantProviderInterface.php
│ ├── ProviderFactoryInterface.php
│ ├── LiteralProvider.php
│ ├── EnvProvider.php
│ ├── ProviderType.php
│ ├── CacheDecoratorProvider.php
│ ├── ConstantProvider.php
│ ├── CallbackProvider.php
│ ├── ProcessProvider.php
│ ├── IncludeProvider.php
│ ├── EscapeDecoratorProvider.php
│ ├── LoggerDecoratorProvider.php
│ ├── AutoloaderDecoratorProvider.php
│ └── ProviderFactory.php
├── EventHandler
│ ├── EventHandlerFactoryInterface.php
│ ├── NullEventHandler.php
│ ├── EventHandlerInterface.php
│ ├── EventHandlerFactory.php
│ ├── PreCommandRunHandler.php
│ └── LegacyEventHandler.php
├── Transformer
│ ├── TransformerInterface.php
│ ├── NullTransformer.php
│ ├── TransformerFactoryInterface.php
│ ├── Transformer.php
│ ├── TransformerCollection.php
│ ├── TransformerFactory.php
│ └── TransformerManager.php
├── Config
│ ├── PluginConfigurationInterface.php
│ ├── SubstitutionConfigurationInterface.php
│ ├── PluginConfiguration.php
│ ├── SubstitutionConfiguration.php
│ └── AbstractConfiguration.php
├── Logger
│ ├── VerboseLogger.php
│ ├── DefaultLogger.php
│ ├── LoggerFactory.php
│ └── VeryVerboseLogger.php
├── utils-functions.php
├── Utils
│ ├── NonRewindableIterator.php
│ └── CommandHelper.php
└── SubstitutionPlugin.php
├── LICENSE
└── composer.json
/src/Provider/ProviderInterface.php:
--------------------------------------------------------------------------------
1 | value = $value;
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function getValue()
22 | {
23 | return $this->value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Provider/EnvProvider.php:
--------------------------------------------------------------------------------
1 | envVarName = $envVarName;
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function getValue()
22 | {
23 | return getenv($this->envVarName);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/EventHandler/EventHandlerInterface.php:
--------------------------------------------------------------------------------
1 | isDebug():
18 | case $io->isVeryVerbose():
19 | return new VeryVerboseLogger($io);
20 | case $io->isVerbose():
21 | return new VerboseLogger($io);
22 | default:
23 | return new DefaultLogger($io);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Provider/ProviderType.php:
--------------------------------------------------------------------------------
1 | isInternal();
24 | } catch (\ReflectionException $e) {
25 | return false;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Provider/CacheDecoratorProvider.php:
--------------------------------------------------------------------------------
1 | provider = $provider;
19 | }
20 |
21 | /**
22 | * @inheritDoc
23 | */
24 | public function getValue()
25 | {
26 | if (!$this->hasValue) {
27 | $this->value = $this->provider->getValue();
28 | $this->hasValue = true;
29 | }
30 |
31 | return $this->value;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Provider/ConstantProvider.php:
--------------------------------------------------------------------------------
1 | constantName = $constantName;
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function mustAutoload()
22 | {
23 | return !defined($this->constantName);
24 | }
25 |
26 | /**
27 | * @inheritDoc
28 | */
29 | public function getValue()
30 | {
31 | if (!defined($this->constantName)) {
32 | throw new \InvalidArgumentException('Value is not a constant: ' . $this->constantName);
33 | }
34 |
35 | return constant($this->constantName);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Provider/CallbackProvider.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function getValue()
22 | {
23 | if (!is_callable($this->callback)) {
24 | throw new \InvalidArgumentException('Value is not callable: ' . $this->callback);
25 | }
26 |
27 | return call_user_func($this->callback);
28 | }
29 |
30 | /**
31 | * @inheritDoc
32 | */
33 | public function mustAutoload()
34 | {
35 | return !\SubstitutionPlugin\isInternalCallback($this->callback);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Transformer/Transformer.php:
--------------------------------------------------------------------------------
1 | placeholder = (string) $placeholder;
22 | $this->provider = $provider;
23 | }
24 |
25 | /**
26 | * @inheritDoc
27 | */
28 | public function transform($value)
29 | {
30 | if ($this->placeholder === '' || strpos($value, $this->placeholder) === false) {
31 | return $value;
32 | }
33 |
34 | return str_replace($this->placeholder, $this->provider->getValue(), $value);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Transformer/TransformerCollection.php:
--------------------------------------------------------------------------------
1 | transformers = $transformers;
16 | }
17 |
18 | /**
19 | * @param TransformerInterface $transformer
20 | * @return void
21 | */
22 | public function addTransformer(TransformerInterface $transformer)
23 | {
24 | $this->transformers[] = $transformer;
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function transform($value)
31 | {
32 | foreach ($this->transformers as $transformer) {
33 | $value = $transformer->transform($value);
34 | }
35 |
36 | return $value;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Provider/ProcessProvider.php:
--------------------------------------------------------------------------------
1 | command = $command;
18 | }
19 |
20 | /**
21 | * @inheritDoc
22 | */
23 | public function getValue()
24 | {
25 | $processExecutor = new ProcessExecutor();
26 | $output = '';
27 | $exitCode = $processExecutor->execute($this->command, $output);
28 | $output = is_array($output) ? implode(PHP_EOL, $output) : (string) $output;
29 | if ($exitCode > 0) {
30 | $message = sprintf('Error executing command "%s"', $this->command);
31 | if (!empty($output)) {
32 | $message .= ': ' . $output;
33 | }
34 | throw new \RuntimeException($message, $exitCode);
35 | }
36 |
37 | return $output;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Provider/IncludeProvider.php:
--------------------------------------------------------------------------------
1 | path = $path;
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function getValue()
22 | {
23 | if (stream_resolve_include_path($this->path) === false) {
24 | throw new \InvalidArgumentException('Cannot include file ' . $this->path);
25 | }
26 |
27 | return returnInclude($this->path);
28 | }
29 |
30 | /**
31 | * @inheritDoc
32 | */
33 | public function mustAutoload()
34 | {
35 | return true;
36 | }
37 | }
38 |
39 | /**
40 | * Scope isolated include.
41 | *
42 | * Prevents access to $this/self from included files.
43 | *
44 | * @param string $file
45 | * @return mixed
46 | */
47 | function returnInclude($file)
48 | {
49 | return include $file;
50 | }
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2024 Fabien Villepinte
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Provider/EscapeDecoratorProvider.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
20 | $this->provider = $provider;
21 | }
22 |
23 | /**
24 | * @inheritDoc
25 | */
26 | public function getValue()
27 | {
28 | if (!is_callable($this->callback)) {
29 | throw new \InvalidArgumentException('The escape callback is not callable: ' . $this->callback);
30 | }
31 |
32 | return call_user_func($this->callback, $this->provider->getValue());
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function mustAutoload()
39 | {
40 | return !\SubstitutionPlugin\isInternalCallback($this->callback);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/EventHandler/EventHandlerFactory.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
22 | $this->configuration = $configuration;
23 | }
24 |
25 | /**
26 | * @inheritDoc
27 | */
28 | public function getEventHandler()
29 | {
30 | switch (true) {
31 | case !$this->configuration->isEnabled():
32 | return new NullEventHandler();
33 | case defined('Composer\\Plugin\\PluginEvents::PRE_COMMAND_RUN'):
34 | return new PreCommandRunHandler($this->callback, $this->configuration);
35 | default:
36 | return new LegacyEventHandler($this->callback);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Provider/LoggerDecoratorProvider.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
25 | $this->configuration = $configuration;
26 | $this->provider = $provider;
27 | }
28 |
29 | /**
30 | * @inheritDoc
31 | */
32 | public function getValue()
33 | {
34 | $value = $this->provider->getValue();
35 |
36 | if ($value === null) {
37 | $this->logger->debug(sprintf(
38 | 'The value replacing "%s" is null.',
39 | $this->configuration->getPlaceholder()
40 | ));
41 | return '';
42 | }
43 |
44 | if (!is_scalar($value)) {
45 | $this->logger->error(sprintf(
46 | 'The value replacing "%s" must be a string. "%s" received.',
47 | $this->configuration->getPlaceholder(),
48 | gettype($value)
49 | ));
50 | return '';
51 | }
52 |
53 | $this->logger->debug(sprintf(
54 | 'The value replacing "%s" is: %s',
55 | $this->configuration->getPlaceholder(),
56 | $value
57 | ));
58 | return (string) $value;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Provider/AutoloaderDecoratorProvider.php:
--------------------------------------------------------------------------------
1 | composer = $composer;
28 | $this->logger = $logger;
29 | $this->provider = $provider;
30 | }
31 |
32 | /**
33 | * @inheritDoc
34 | */
35 | public function getValue()
36 | {
37 | if (!self::$autoload) {
38 | $this->autoload();
39 | self::$autoload = true;
40 | }
41 |
42 | return $this->provider->getValue();
43 | }
44 |
45 | /**
46 | * @return void
47 | */
48 | private function autoload()
49 | {
50 | $files = array();
51 | if ($this->composer->getConfig()->has('vendor-dir')) {
52 | $files[] = $this->composer->getConfig()->get('vendor-dir') . '/autoload.php';
53 | }
54 |
55 | foreach ($files as $file) {
56 | $this->logger->debug('Try including autoloader at: ' . $file);
57 | if (stream_resolve_include_path($file) !== false) {
58 | includeFile($file);
59 | return;
60 | }
61 | }
62 |
63 | $this->logger->warning('Cannot include autoloader');
64 | }
65 | }
66 |
67 | /**
68 | * Scope isolated include.
69 | *
70 | * Prevents access to $this/self from included files.
71 | *
72 | * @param string $file
73 | * @return void
74 | */
75 | function includeFile($file)
76 | {
77 | include $file;
78 | }
79 |
--------------------------------------------------------------------------------
/src/Utils/NonRewindableIterator.php:
--------------------------------------------------------------------------------
1 | */
8 | private $values = array();
9 |
10 | /** @var string|null */
11 | private $value = null;
12 |
13 | /** @var int */
14 | private $total = 0;
15 |
16 | /**
17 | * @param string[] $values
18 | */
19 | public function __construct(array $values = array())
20 | {
21 | $this->addAll($values);
22 | }
23 |
24 | /**
25 | * @param string $value
26 | * @return void
27 | */
28 | public function add($value)
29 | {
30 | if (!isset($this->values[$value])) {
31 | if ($this->value === null) {
32 | $this->value = $value;
33 | }
34 | $this->values[$value] = $this->total;
35 | $this->total++;
36 | }
37 | }
38 |
39 | /**
40 | * @param string[] $values
41 | * @return void
42 | */
43 | public function addAll(array $values)
44 | {
45 | foreach ($values as $value) {
46 | $this->add($value);
47 | }
48 | }
49 |
50 | /**
51 | * @return string|null
52 | */
53 | #[\ReturnTypeWillChange]
54 | public function current()
55 | {
56 | return $this->value;
57 | }
58 |
59 | /**
60 | * @return void
61 | */
62 | #[\ReturnTypeWillChange]
63 | public function next()
64 | {
65 | next($this->values);
66 | $this->value = key($this->values);
67 | }
68 |
69 | /**
70 | * @return int
71 | */
72 | #[\ReturnTypeWillChange]
73 | public function key()
74 | {
75 | return $this->values[$this->value];
76 | }
77 |
78 | /**
79 | * @return bool
80 | */
81 | #[\ReturnTypeWillChange]
82 | public function valid()
83 | {
84 | return $this->value !== null;
85 | }
86 |
87 | /**
88 | * @return void
89 | */
90 | #[\ReturnTypeWillChange]
91 | public function rewind()
92 | {
93 | // no rewind
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/EventHandler/PreCommandRunHandler.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
28 | $this->configuration = $configuration;
29 | $this->cmdHelper = new CommandHelper();
30 | }
31 |
32 | /**
33 | * @inheritDoc
34 | */
35 | public function getSubscribedEvents()
36 | {
37 | return array(
38 | PluginEvents::PRE_COMMAND_RUN => array(
39 | array('onPreCommandRun', $this->configuration->getPriority()),
40 | ),
41 | );
42 | }
43 |
44 | /**
45 | * @param PreCommandRunEvent $event
46 | * @return void
47 | */
48 | public function onPreCommandRun(PreCommandRunEvent $event)
49 | {
50 | call_user_func($this->callback, $this->getScripts($event));
51 | }
52 |
53 | /**
54 | * @param PreCommandRunEvent $event
55 | * @return array
56 | */
57 | private function getScripts(PreCommandRunEvent $event)
58 | {
59 | return $this->cmdHelper->getScripts($event->getCommand(), $event->getInput());
60 | }
61 |
62 | /**
63 | * @inheritDoc
64 | */
65 | public function activate()
66 | {
67 | // Nothing to do here
68 | }
69 |
70 | /**
71 | * @inheritDoc
72 | */
73 | public function deactivate()
74 | {
75 | // Nothing to do here
76 | }
77 |
78 | /**
79 | * @inheritDoc
80 | */
81 | public function uninstall()
82 | {
83 | // Nothing to do here
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Transformer/TransformerFactory.php:
--------------------------------------------------------------------------------
1 | providerFactory = $providerFactory;
23 | $this->logger = $logger;
24 | }
25 |
26 | /**
27 | * @inheritDoc
28 | */
29 | public function getTransformer(PluginConfigurationInterface $configuration)
30 | {
31 | $nbSubstitutions = count($configuration->getMapping());
32 | if ($nbSubstitutions > 1) {
33 | $transformer = new TransformerCollection();
34 | foreach ($configuration->getMapping() as $conf) {
35 | $transformer->addTransformer($this->buildTransformer($conf));
36 | }
37 | } elseif ($nbSubstitutions === 1) {
38 | /** @var SubstitutionConfigurationInterface $conf */
39 | $conf = current($configuration->getMapping());
40 | $transformer = $this->buildTransformer($conf);
41 | } else {
42 | // not supposed to happen
43 | $this->logger->error('At least one substitution expected');
44 | $transformer = new NullTransformer();
45 | }
46 |
47 | return $transformer;
48 | }
49 |
50 | /**
51 | * @param SubstitutionConfigurationInterface $configuration
52 | * @return TransformerInterface
53 | */
54 | private function buildTransformer(SubstitutionConfigurationInterface $configuration)
55 | {
56 | $provider = $this->providerFactory->getProvider($configuration);
57 |
58 | if ($provider === null) {
59 | return new NullTransformer();
60 | }
61 |
62 | return new Transformer($configuration->getPlaceholder(), $provider);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Transformer/TransformerManager.php:
--------------------------------------------------------------------------------
1 | transformer = $transformerFactory->getTransformer($config);
23 | $this->logger = $logger;
24 | }
25 |
26 | /**
27 | * @param array $scripts
28 | * @param string[] $scriptNames
29 | * @return array
30 | */
31 | public function applySubstitutions(array $scripts, array $scriptNames)
32 | {
33 | $scriptsToTransform = new NonRewindableIterator($scriptNames);
34 |
35 | foreach ($scriptsToTransform as $scriptName) {
36 | if (!isset($scripts[$scriptName])) {
37 | continue;
38 | }
39 |
40 | $this->logger->debug('Apply substitution on script ' . $scriptName);
41 | $listeners = &$scripts[$scriptName];
42 | foreach ($listeners as &$listener) {
43 | $listener = $this->transformer->transform($listener);
44 |
45 | if (self::tryExtractScript($listener, $script)) {
46 | $scriptsToTransform->add($script);
47 | }
48 | }
49 | }
50 |
51 | return $scripts;
52 | }
53 |
54 | /**
55 | * @param string $listener
56 | * @param string $script
57 | * @return bool
58 | */
59 | private static function tryExtractScript($listener, &$script = '')
60 | {
61 | if (!isset($listener[0]) || $listener[0] !== '@') {
62 | return false;
63 | }
64 |
65 | // split on white-spaces
66 | $parts = preg_split('/\s+/', substr($listener, 1), -1, PREG_SPLIT_NO_EMPTY);
67 |
68 | if (empty($parts)) {
69 | return false;
70 | }
71 |
72 | $script = current($parts);
73 |
74 | return true;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "villfa/composer-substitution-plugin",
3 | "description": "Composer plugin replacing placeholders in the scripts section by dynamic values",
4 | "license": [
5 | "MIT"
6 | ],
7 | "type": "composer-plugin",
8 | "keywords": [
9 | "composer",
10 | "plugin",
11 | "substitution",
12 | "replacement",
13 | "scripts"
14 | ],
15 | "authors": [
16 | {
17 | "name": "Fabien VILLEPINTE",
18 | "email": "fabien.villepinte@gmail.com"
19 | }
20 | ],
21 | "homepage": "https://github.com/villfa/composer-substitution-plugin",
22 | "require": {
23 | "php": ">=5.3.2",
24 | "ext-ctype": "*",
25 | "ext-json": "*",
26 | "composer-plugin-api": "^1.0 || ^2.0",
27 | "psr/log": "^1.1"
28 | },
29 | "require-dev": {
30 | "composer/composer": ">=1.1",
31 | "phpunit/phpunit": "4.8.36 || 5.7.27 || 6.5.14 || ^8.5.21 || ^9.5.10"
32 | },
33 | "minimum-stability": "stable",
34 | "prefer-stable": true,
35 | "autoload": {
36 | "psr-4": {
37 | "SubstitutionPlugin\\": "src/"
38 | },
39 | "files": [
40 | "src/utils-functions.php"
41 | ]
42 | },
43 | "autoload-dev": {
44 | "psr-4": {
45 | "SubstitutionPlugin\\": [
46 | "tests/e2e/",
47 | "tests/unit/"
48 | ]
49 | },
50 | "files": [
51 | "tests/BaseTestCase.php"
52 | ],
53 | "exclude-from-classmap": [
54 | "**/vendor/"
55 | ]
56 | },
57 | "config": {
58 | "optimize-autoloader": true,
59 | "sort-packages": true
60 | },
61 | "extra": {
62 | "class": "SubstitutionPlugin\\SubstitutionPlugin"
63 | },
64 | "scripts": {
65 | "test": [
66 | "@composer validate --no-interaction --strict",
67 | "@test:unit",
68 | "@test:e2e"
69 | ],
70 | "test:bc": "phpunit --testsuite bc_tests",
71 | "test:e2e": "phpunit --testsuite e2e_tests --stop-on-failure --debug",
72 | "test:unit": "phpunit --testsuite unit_tests"
73 | },
74 | "scripts-descriptions": {
75 | "test": "Validates and tests the plugin.",
76 | "test:bc": "Runs backward compatibility tests",
77 | "test:e2e": "Runs end to end tests",
78 | "test:unit": "Runs unit tests"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Provider/ProviderFactory.php:
--------------------------------------------------------------------------------
1 | composer = $composer;
20 | $this->logger = $logger;
21 | }
22 |
23 | /**
24 | * @inheritDoc
25 | */
26 | public function getProvider(SubstitutionConfigurationInterface $configuration)
27 | {
28 | try {
29 | $this->logger->debug(
30 | 'Build provider for "{placeholder}" with type {type}',
31 | array(
32 | 'placeholder' => $configuration->getPlaceholder(),
33 | 'type' => $configuration->getType(),
34 | )
35 | );
36 | return $this->buildProvider($configuration);
37 | } catch (\Exception $e) {
38 | $this->logger->error(sprintf(
39 | 'Error with configuration extra.substitution.mapping.%s: %s',
40 | $configuration->getPlaceholder(),
41 | $e->getMessage()
42 | ));
43 | return null;
44 | }
45 | }
46 |
47 | /**
48 | * @param SubstitutionConfigurationInterface $configuration
49 | * @return ProviderInterface|null
50 | */
51 | private function buildProvider(SubstitutionConfigurationInterface $configuration)
52 | {
53 | switch ($configuration->getType()) {
54 | case ProviderType::LITERAL:
55 | $provider = new LiteralProvider($configuration->getValue());
56 | break;
57 | case ProviderType::CALLBACK:
58 | $provider = new CallbackProvider($configuration->getValue());
59 | break;
60 | case ProviderType::ENV:
61 | $provider = new EnvProvider($configuration->getValue());
62 | break;
63 | case ProviderType::INCLUDE_PHP:
64 | $provider = new IncludeProvider($configuration->getValue());
65 | break;
66 | case ProviderType::CONSTANT:
67 | $provider = new ConstantProvider($configuration->getValue());
68 | break;
69 | case ProviderType::PROCESS:
70 | $provider = new ProcessProvider($configuration->getValue());
71 | break;
72 | default:
73 | // not supposed to happen
74 | $this->logger->critical('Invalid type: ' . $configuration->getType());
75 | return null;
76 | }
77 |
78 | if ($configuration->getEscapeCallback() !== null) {
79 | $provider = new EscapeDecoratorProvider($configuration->getEscapeCallback(), $provider);
80 | }
81 |
82 | if ($provider instanceof AutoloadDependentProviderInterface && $provider->mustAutoload()) {
83 | $provider = new AutoloaderDecoratorProvider($this->composer, $this->logger, $provider);
84 | }
85 |
86 | $provider = new LoggerDecoratorProvider($this->logger, $configuration, $provider);
87 |
88 | if ($configuration->isCached()) {
89 | $provider = new CacheDecoratorProvider($provider);
90 | }
91 |
92 | return $provider;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/EventHandler/LegacyEventHandler.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
25 | $this->cmdHelper = new CommandHelper();
26 | }
27 |
28 | /**
29 | * @inheritDoc
30 | */
31 | public function getSubscribedEvents()
32 | {
33 | return array();
34 | }
35 |
36 | public function activate()
37 | {
38 | try {
39 | $scriptNames = $this->getScripts();
40 | } catch (\Exception $e) {
41 | $scriptNames = array();
42 | }
43 |
44 | call_user_func($this->callback, $scriptNames);
45 | }
46 |
47 | /**
48 | * @return array
49 | */
50 | private function getScripts()
51 | {
52 | $definition = new InputDefinition(array(
53 | // default definition
54 | new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
55 |
56 | new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
57 | new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
58 | new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
59 | new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
60 | new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
61 | new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
62 | new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
63 |
64 | // custom Composer definition
65 | new InputOption('--profile', null, InputOption::VALUE_NONE),
66 | new InputOption('--no-plugins', null, InputOption::VALUE_NONE),
67 | new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED),
68 | new InputOption('--no-cache', null, InputOption::VALUE_NONE),
69 |
70 | // run-script
71 | new InputArgument('script', InputArgument::OPTIONAL),
72 | new InputOption('list', 'l', InputOption::VALUE_NONE),
73 | ));
74 |
75 | $input = new ArgvInput(null, $definition);
76 | $cmd = $input->getArgument('command');
77 |
78 | if (
79 | $input->getOption('version')
80 | || $input->getOption('help')
81 | || empty($cmd)
82 | || !is_string($cmd)
83 | ) {
84 | return array();
85 | }
86 |
87 | return $this->cmdHelper->getScripts($cmd, $input);
88 | }
89 |
90 | public function deactivate()
91 | {
92 | // Nothing to do here
93 | }
94 |
95 | public function uninstall()
96 | {
97 | // Nothing to do here
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Config/PluginConfiguration.php:
--------------------------------------------------------------------------------
1 | = 2.2
22 | $this->enabled = version_compare(Composer::VERSION, '2.2', '>=');
23 | self::setLogger($logger);
24 |
25 | if (!isset($extra['substitution'])) {
26 | self::$logger->debug('Configuration extra.substitution is missing.');
27 | $this->enabled = false;
28 | } elseif (!is_array($extra['substitution'])) {
29 | self::$logger->warning('Configuration extra.substitution must be an object.');
30 | $this->enabled = false;
31 | } else {
32 | $this->parseConfiguration($extra['substitution']);
33 | }
34 | }
35 |
36 | /**
37 | * @param array $conf
38 | * @return void
39 | */
40 | private function parseConfiguration(array $conf)
41 | {
42 | if (isset($conf['enable'])) {
43 | $this->enabled = self::parseBool('enable', $conf['enable'], $this->enabled);
44 | }
45 | if (!$this->enabled) {
46 | // no need to go further
47 | return;
48 | }
49 | if (!isset($conf['mapping'])) {
50 | $this->enabled = false;
51 | self::$logger->notice('Configuration extra.substitution.mapping missing. Plugin disabled.');
52 | return;
53 | } elseif (!is_array($conf['mapping'])) {
54 | $this->enabled = false;
55 | self::$logger->warning('Configuration extra.substitution.mapping must be an object. Plugin disabled.');
56 | return;
57 | } else {
58 | $this->mapping = $this->parseMapping($conf['mapping']);
59 | }
60 |
61 | if (count($this->mapping) === 0) {
62 | $this->enabled = false;
63 | self::$logger->notice('Configuration extra.substitution.mapping empty. Plugin disabled.');
64 | return;
65 | }
66 |
67 | if (isset($conf['priority'])) {
68 | $this->priority = (int) self::parseInt('priority', $conf['priority'], $this->priority);
69 | }
70 | }
71 |
72 | /**
73 | * @param array $conf
74 | * @return SubstitutionConfiguration[]
75 | */
76 | private function parseMapping(array $conf)
77 | {
78 | $mapping = array();
79 |
80 | foreach ($conf as $placeholder => $value) {
81 | $substitution = SubstitutionConfiguration::parseConfiguration((string)$placeholder, $value, self::$logger);
82 | if ($substitution !== null) {
83 | $mapping[] = $substitution;
84 | }
85 | }
86 |
87 | return $mapping;
88 | }
89 |
90 | /**
91 | * @inheritDoc
92 | */
93 | public function isEnabled()
94 | {
95 | return $this->enabled;
96 | }
97 |
98 | /**
99 | * @inheritDoc
100 | */
101 | public function getPriority()
102 | {
103 | return $this->priority;
104 | }
105 |
106 | /**
107 | * @inheritDoc
108 | */
109 | public function getMapping()
110 | {
111 | return $this->mapping;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Config/SubstitutionConfiguration.php:
--------------------------------------------------------------------------------
1 | placeholder = $placeholder;
39 | $this->type = $type;
40 | $this->value = $value;
41 | $this->cached = $cached;
42 | $this->escapeCallback = $escapeCallback;
43 | }
44 |
45 | /**
46 | * @param string $placeholder
47 | * @param mixed $conf
48 | * @param LoggerInterface $logger
49 | * @return SubstitutionConfiguration|null
50 | */
51 | public static function parseConfiguration($placeholder, $conf, LoggerInterface $logger)
52 | {
53 | self::setLogger($logger);
54 |
55 | if ($placeholder === '') {
56 | self::$logger->warning("Configuration extra.substitution.mapping doesn't accept empty keys.");
57 | return null;
58 | }
59 |
60 | if (!is_array($conf)) {
61 | self::$logger->warning("Configuration extra.substitution.mapping.$placeholder must be an object.");
62 | return null;
63 | }
64 |
65 | if (!isset($conf['value'])) {
66 | self::$logger->warning("Configuration extra.substitution.mapping.$placeholder.value is missing.");
67 | return null;
68 | }
69 |
70 | if (!isset($conf['type'])) {
71 | self::$logger->warning("Configuration extra.substitution.mapping.$placeholder.type is missing.");
72 | return null;
73 | }
74 |
75 | if (
76 | null === ($value = self::parseString("mapping.$placeholder.value", $conf['value']))
77 | || null === ($type = self::parseEnum("mapping.$placeholder.type", $conf['type'], ProviderType::all()))
78 | ) {
79 | return null;
80 | }
81 |
82 | $cached = isset($conf['cached']) && self::parseBool("mapping.$placeholder.cached", $conf['cached']);
83 | $escape = isset($conf['escape']) ? self::parseString("mapping.$placeholder.escape", $conf['escape']) : null;
84 |
85 | return new SubstitutionConfiguration($placeholder, $type, $value, $cached, $escape);
86 | }
87 |
88 | /**
89 | * @inheritDoc
90 | */
91 | public function getPlaceholder()
92 | {
93 | return $this->placeholder;
94 | }
95 |
96 | /**
97 | * @inheritDoc
98 | */
99 | public function getType()
100 | {
101 | return $this->type;
102 | }
103 |
104 | /**
105 | * @inheritDoc
106 | */
107 | public function getValue()
108 | {
109 | return $this->value;
110 | }
111 |
112 | /**
113 | * @inheritDoc
114 | */
115 | public function isCached()
116 | {
117 | return $this->cached;
118 | }
119 |
120 | /**
121 | * @inheritDoc
122 | */
123 | public function getEscapeCallback()
124 | {
125 | return $this->escapeCallback;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Utils/CommandHelper.php:
--------------------------------------------------------------------------------
1 | normalizeName($command);
19 |
20 | if ($command === 'run-script') {
21 | if ($input->getOption('list')) {
22 | return array();
23 | }
24 |
25 | $scriptNames = array($input->getArgument('script'));
26 | } elseif (!$this->tryGetScriptsFromCommand($command, $scriptNames)) {
27 | $scriptNames = array($command);
28 | }
29 |
30 | $scriptNames = array_filter($scriptNames);
31 | $scriptNames[] = 'command';
32 |
33 | return $scriptNames;
34 | }
35 |
36 | /**
37 | * @param string|null $commandName
38 | * @return string
39 | */
40 | private function normalizeName($commandName)
41 | {
42 | global $application;
43 | $app = $application === null ? new Application() : $application;
44 |
45 | try {
46 | $cmd = $app->find($commandName);
47 | $name = $cmd->getName();
48 | $getDefaultNameFunc = array($cmd, 'getDefaultName');
49 | if ($name === null && is_callable($getDefaultNameFunc)) {
50 | $name = call_user_func($getDefaultNameFunc);
51 | }
52 | if ($name !== null) {
53 | return $name;
54 | }
55 | } catch (CommandNotFoundException $e) {
56 | }
57 |
58 | return (string) $commandName;
59 | }
60 |
61 | /**
62 | * @param string $command
63 | * @param array $scriptNames
64 | * @return bool
65 | */
66 | private function tryGetScriptsFromCommand($command, &$scriptNames = array())
67 | {
68 | switch ($command) {
69 | case 'install':
70 | $scriptNames = array(
71 | 'pre-install-cmd',
72 | 'post-install-cmd',
73 | 'pre-autoload-dump',
74 | 'post-autoload-dump',
75 | 'pre-dependencies-solving',
76 | 'post-dependencies-solving',
77 | 'pre-package-install',
78 | 'post-package-install',
79 | 'pre-operations-exec',
80 | 'pre-pool-create',
81 | 'post-file-download',
82 | );
83 | break;
84 | case 'update':
85 | $scriptNames = array(
86 | 'pre-update-cmd',
87 | 'post-update-cmd',
88 | 'pre-autoload-dump',
89 | 'post-autoload-dump',
90 | 'pre-package-update',
91 | 'post-package-update',
92 | 'pre-package-uninstall',
93 | 'post-package-uninstall',
94 | 'pre-operations-exec',
95 | 'pre-pool-create',
96 | 'post-file-download',
97 | );
98 | break;
99 | case 'remove':
100 | $scriptNames = array(
101 | 'pre-package-uninstall',
102 | 'post-package-uninstall',
103 | 'pre-operations-exec',
104 | );
105 | break;
106 | case 'dump-autoload':
107 | $scriptNames = array(
108 | 'pre-autoload-dump',
109 | 'post-autoload-dump'
110 | );
111 | break;
112 | case 'status':
113 | $scriptNames = array(
114 | 'pre-status-cmd',
115 | 'post-status-cmd',
116 | );
117 | break;
118 | case 'archive':
119 | $scriptNames = array(
120 | 'pre-archive-cmd',
121 | 'post-archive-cmd',
122 | );
123 | break;
124 | default:
125 | return false;
126 | }
127 |
128 | return true;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/Config/AbstractConfiguration.php:
--------------------------------------------------------------------------------
1 | notice("Configuration extra.substitution.$key should be a boolean.");
35 | return true;
36 | }
37 | if ($value === 0 || (is_string($value) && in_array(strtolower($value), array('false', 'off', '0')))) {
38 | self::$logger->notice("Configuration extra.substitution.$key should be a boolean.");
39 | return false;
40 | }
41 |
42 | self::$logger->warning("Invalid value for configuration extra.substitution.$key.");
43 | return $defaultValue;
44 | }
45 |
46 | /**
47 | * @param string $key
48 | * @param mixed $value
49 | * @param string|null $defaultValue
50 | * @return string|null
51 | */
52 | protected static function parseString($key, $value, $defaultValue = null)
53 | {
54 | if (!is_scalar($value)) {
55 | self::$logger->warning("Configuration extra.substitution.$key must be a string.");
56 | return $defaultValue;
57 | }
58 | if (!is_string($value)) {
59 | self::$logger->notice("Configuration extra.substitution.$key should be a string.");
60 | }
61 |
62 | return (string) $value;
63 | }
64 |
65 | /**
66 | * @param string $key
67 | * @param mixed $value
68 | * @param string[] $acceptedValues
69 | * @param string|null $defaultValue
70 | * @return string|null
71 | */
72 | protected static function parseEnum($key, $value, array $acceptedValues, $defaultValue = null)
73 | {
74 | if (is_string($value)) {
75 | if (in_array($value, $acceptedValues)) {
76 | return $value;
77 | }
78 | if (in_array(strtolower($value), $acceptedValues)) {
79 | self::$logger->notice("Configuration extra.substitution.$key ($value) should be in lowercase.");
80 | return strtolower($value);
81 | }
82 | }
83 |
84 | self::$logger->warning(
85 | "Invalid value for configuration extra.substitution.$key. Accepted values: {values}. {default}",
86 | array(
87 | 'values' => function () use ($acceptedValues) {
88 | return implode(', ', $acceptedValues);
89 | },
90 | 'default' => function () use ($defaultValue) {
91 | return $defaultValue === null ? '' : "Default to '$defaultValue'.";
92 | },
93 | )
94 | );
95 |
96 | return $defaultValue;
97 | }
98 |
99 | /**
100 | * @param string $key
101 | * @param mixed $value
102 | * @param int|null $defaultValue
103 | * @return int|null
104 | */
105 | protected static function parseInt($key, $value, $defaultValue = null)
106 | {
107 | if (is_int($value)) {
108 | return $value;
109 | }
110 | if (is_float($value)) {
111 | if ($value != (int) $value) {
112 | self::$logger->notice("Configuration extra.substitution.$key must be an integer.");
113 | }
114 | return (int) $value;
115 | }
116 | if (is_string($value) && is_numeric($value)) {
117 | if (!ctype_digit($value)) {
118 | self::$logger->notice("Configuration extra.substitution.$key must be an integer.");
119 | }
120 | return (int) $value;
121 | }
122 |
123 | self::$logger->warning(
124 | "Invalid value for configuration extra.substitution.$key. It must be an integer. {default}",
125 | array(
126 | 'default' => function () use ($defaultValue) {
127 | return $defaultValue === null ? '' : "Default to $defaultValue.";
128 | },
129 | )
130 | );
131 |
132 | return $defaultValue;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/SubstitutionPlugin.php:
--------------------------------------------------------------------------------
1 | includeRequiredFiles();
42 | $this->composer = $composer;
43 | $this->logger = $logger = LoggerFactory::getLogger($io);
44 | $config = new PluginConfiguration($composer->getPackage()->getExtra(), $logger);
45 |
46 | $this->logger->info(
47 | 'Plugin ' . ($config->isEnabled() ? 'enabled. {priority}' : 'disabled.'),
48 | array(
49 | 'priority' => function () use ($config) {
50 | return 'Priority set to ' . strval($config->getPriority()) . '.';
51 | },
52 | )
53 | );
54 | $providerFactory = new ProviderFactory($composer, $logger);
55 | $transformerFactory = new TransformerFactory($providerFactory, $logger);
56 | $this->transformerManager = new TransformerManager($transformerFactory, $config, $logger);
57 | $eventHandlerFactory = new EventHandlerFactory(array($this, 'execute'), $config);
58 | self::$eventHandler = $eventHandlerFactory->getEventHandler();
59 | self::$eventHandler->activate();
60 | }
61 |
62 | /**
63 | * @param Composer $composer
64 | * @param IOInterface $io
65 | * @return void
66 | */
67 | public function deactivate(Composer $composer, IOInterface $io)
68 | {
69 | self::$eventHandler->deactivate();
70 | }
71 |
72 | /**
73 | * @param Composer $composer
74 | * @param IOInterface $io
75 | * @return void
76 | */
77 | public function uninstall(Composer $composer, IOInterface $io)
78 | {
79 | self::$eventHandler->uninstall();
80 | }
81 |
82 | /**
83 | * @inheritDoc
84 | */
85 | public static function getSubscribedEvents()
86 | {
87 | return self::$eventHandler->getSubscribedEvents();
88 | }
89 |
90 | /**
91 | * This is needed because getSubscribedEvents() can not return callbacks pointing to other classes.
92 | *
93 | * @param string $name
94 | * @param array $args
95 | * @return mixed
96 | */
97 | public function __call($name, array $args)
98 | {
99 | /** @var callable $callback */
100 | $callback = array(self::$eventHandler, $name);
101 |
102 | return call_user_func_array($callback, $args);
103 | }
104 |
105 | /**
106 | * @param string[] $scriptNames
107 | * @return void
108 | */
109 | public function execute(array $scriptNames) {
110 | if (empty($scriptNames)) {
111 | return;
112 | }
113 |
114 | $package = $this->composer->getPackage();
115 | if ($package instanceof AliasPackage) {
116 | $package = $package->getAliasOf();
117 | }
118 |
119 | if ($package instanceof CompletePackage) {
120 | $package->setScripts($this->applySubstitutions($package->getScripts(), $scriptNames));
121 | }
122 | }
123 |
124 | /**
125 | * @param array $scripts
126 | * @param string[] $scriptNames
127 | * @return array
128 | */
129 | private function applySubstitutions(array $scripts, array $scriptNames)
130 | {
131 | $this->logger->info('Start applying substitutions on scripts: ' . implode(', ', $scriptNames));
132 |
133 | return $this->transformerManager->applySubstitutions($scripts, $scriptNames);
134 | }
135 |
136 | /**
137 | * @return void
138 | */
139 | private function includeRequiredFiles()
140 | {
141 | require_once __DIR__ . '/utils-functions.php';
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Logger/VeryVerboseLogger.php:
--------------------------------------------------------------------------------
1 | io = $io;
19 | }
20 |
21 | /**
22 | * @inheritDoc
23 | */
24 | public function log($level, $message, array $context = array())
25 | {
26 | $log = self::LOG_PREFIX . $this->interpolate($message, $context);
27 |
28 | switch ($level) {
29 | case LogLevel::DEBUG:
30 | $this->io->write($log);
31 | break;
32 | case LogLevel::INFO:
33 | $this->io->write('' . $log . '');
34 | break;
35 | case LogLevel::NOTICE:
36 | case LogLevel::WARNING:
37 | $this->io->write('' . $log . '');
38 | break;
39 | default:
40 | $this->io->writeError('' . $log . '');
41 | break;
42 | }
43 | }
44 |
45 | /**
46 | * Interpolates context values into the message placeholders.
47 | *
48 | * @author PHP Framework Interoperability Group
49 | *
50 | * @param string $message
51 | * @param array $context
52 | * @return string
53 | */
54 | private function interpolate($message, array $context)
55 | {
56 | if (false === strpos($message, '{')) {
57 | return $message;
58 | }
59 |
60 | $replacements = array();
61 | foreach ($context as $key => $val) {
62 | if (is_callable($val)) {
63 | $val = call_user_func($val);
64 | }
65 | if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
66 | $replacements["{{$key}}"] = $val;
67 | } elseif ($val instanceof \DateTime || $val instanceof \DateTimeInterface) {
68 | $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
69 | } elseif (\is_object($val)) {
70 | $replacements["{{$key}}"] = '[object '.\get_class($val).']';
71 | } else {
72 | $replacements["{{$key}}"] = '['.\gettype($val).']';
73 | }
74 | }
75 |
76 | return strtr($message, $replacements);
77 | }
78 | /**
79 | * System is unusable.
80 | *
81 | * @param string $message
82 | * @param array $context
83 | *
84 | * @return void
85 | */
86 | public function emergency($message, array $context = array())
87 | {
88 | $this->log(LogLevel::EMERGENCY, $message, $context);
89 | }
90 |
91 | /**
92 | * Action must be taken immediately.
93 | *
94 | * Example: Entire website down, database unavailable, etc. This should
95 | * trigger the SMS alerts and wake you up.
96 | *
97 | * @param string $message
98 | * @param array $context
99 | *
100 | * @return void
101 | */
102 | public function alert($message, array $context = array())
103 | {
104 | $this->log(LogLevel::ALERT, $message, $context);
105 | }
106 |
107 | /**
108 | * Critical conditions.
109 | *
110 | * Example: Application component unavailable, unexpected exception.
111 | *
112 | * @param string $message
113 | * @param array $context
114 | *
115 | * @return void
116 | */
117 | public function critical($message, array $context = array())
118 | {
119 | $this->log(LogLevel::CRITICAL, $message, $context);
120 | }
121 |
122 | /**
123 | * Runtime errors that do not require immediate action but should typically
124 | * be logged and monitored.
125 | *
126 | * @param string $message
127 | * @param array $context
128 | *
129 | * @return void
130 | */
131 | public function error($message, array $context = array())
132 | {
133 | $this->log(LogLevel::ERROR, $message, $context);
134 | }
135 |
136 | /**
137 | * Exceptional occurrences that are not errors.
138 | *
139 | * Example: Use of deprecated APIs, poor use of an API, undesirable things
140 | * that are not necessarily wrong.
141 | *
142 | * @param string $message
143 | * @param array $context
144 | *
145 | * @return void
146 | */
147 | public function warning($message, array $context = array())
148 | {
149 | $this->log(LogLevel::WARNING, $message, $context);
150 | }
151 |
152 | /**
153 | * Normal but significant events.
154 | *
155 | * @param string $message
156 | * @param array $context
157 | *
158 | * @return void
159 | */
160 | public function notice($message, array $context = array())
161 | {
162 | $this->log(LogLevel::NOTICE, $message, $context);
163 | }
164 |
165 | /**
166 | * Interesting events.
167 | *
168 | * Example: User logs in, SQL logs.
169 | *
170 | * @param string $message
171 | * @param array $context
172 | *
173 | * @return void
174 | */
175 | public function info($message, array $context = array())
176 | {
177 | $this->log(LogLevel::INFO, $message, $context);
178 | }
179 |
180 | /**
181 | * Detailed debug information.
182 | *
183 | * @param string $message
184 | * @param array $context
185 | *
186 | * @return void
187 | */
188 | public function debug($message, array $context = array())
189 | {
190 | $this->log(LogLevel::DEBUG, $message, $context);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------