├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── Collection.php
├── ComponentInstaller.php
├── ConfigDiscovery.php
├── ConfigDiscovery
├── AbstractDiscovery.php
├── ApplicationConfig.php
├── ConfigAggregator.php
├── DevelopmentConfig.php
├── DevelopmentWorkConfig.php
├── DiscoveryChain.php
├── DiscoveryChainInterface.php
├── DiscoveryInterface.php
├── ExpressiveConfig.php
└── ModulesConfig.php
├── ConfigOption.php
├── Exception
└── RuntimeException.php
└── Injector
├── AbstractInjector.php
├── ApplicationConfigInjector.php
├── ConditionalDiscoveryTrait.php
├── ConfigAggregatorInjector.php
├── ConfigInjectorChain.php
├── DevelopmentConfigInjector.php
├── DevelopmentWorkConfigInjector.php
├── ExpressiveConfigInjector.php
├── InjectorInterface.php
├── ModulesConfigInjector.php
└── NoopInjector.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file, in reverse chronological order by release.
4 |
5 | ## 2.1.3 - TBD
6 |
7 | ### Added
8 |
9 | - Nothing.
10 |
11 | ### Changed
12 |
13 | - Nothing.
14 |
15 | ### Deprecated
16 |
17 | - Nothing.
18 |
19 | ### Removed
20 |
21 | - Nothing.
22 |
23 | ### Fixed
24 |
25 | - Nothing.
26 |
27 | ## 2.1.2 - 2019-09-04
28 |
29 | ### Added
30 |
31 | - [#57](https://github.com/zendframework/zend-component-installer/pull/57) adds support for PHP 7.3.
32 |
33 | ### Changed
34 |
35 | - Nothing.
36 |
37 | ### Deprecated
38 |
39 | - Nothing.
40 |
41 | ### Removed
42 |
43 | - Nothing.
44 |
45 | ### Fixed
46 |
47 | - Nothing.
48 |
49 | ## 2.1.1 - 2018-03-21
50 |
51 | ### Added
52 |
53 | - Nothing.
54 |
55 | ### Changed
56 |
57 | - Nothing.
58 |
59 | ### Deprecated
60 |
61 | - Nothing.
62 |
63 | ### Removed
64 |
65 | - Nothing.
66 |
67 | ### Fixed
68 |
69 | - [#54](https://github.com/zendframework/zend-component-installer/pull/54) fixes
70 | issues when run with symfony/console v4 releases.
71 |
72 | ## 2.1.0 - 2018-02-08
73 |
74 | ### Added
75 |
76 | - [#52](https://github.com/zendframework/zend-component-installer/pull/52) adds
77 | the ability to whitelist packages exposing config providers and/or modules.
78 | When whitelisted, the installer will not prompt to inject configuration, but
79 | instead do it automatically. This is done at the root package level, using the
80 | following configuration:
81 |
82 | ```json
83 | "extra": {
84 | "zf": {
85 | "component-whitelist": [
86 | "some/package"
87 | ]
88 | }
89 | }
90 | ```
91 |
92 | ### Changed
93 |
94 | - Nothing.
95 |
96 | ### Deprecated
97 |
98 | - Nothing.
99 |
100 | ### Removed
101 |
102 | - Nothing.
103 |
104 | ### Fixed
105 |
106 | - Nothing.
107 |
108 | ## 2.0.0 - 2018-02-06
109 |
110 | ### Added
111 |
112 | - Nothing.
113 |
114 | ### Changed
115 |
116 | - [#49](https://github.com/zendframework/zend-component-installer/pull/49)
117 | modifies the default options for installer prompts. If providers and/or
118 | modules are discovered, the installer uses the first discovered as the default
119 | option, instead of the "Do not inject" option. Additionally, the "remember
120 | this selection" prompt now defaults to "y" instead of "n".
121 |
122 | ### Deprecated
123 |
124 | - Nothing.
125 |
126 | ### Removed
127 |
128 | - [#50](https://github.com/zendframework/zend-component-installer/pull/50)
129 | removes support for PHP versions 5.6 and 7.0.
130 |
131 | ### Fixed
132 |
133 | - Nothing.
134 |
135 | ## 1.1.1 - 2018-01-11
136 |
137 | ### Added
138 |
139 | - Nothing.
140 |
141 | ### Changed
142 |
143 | - Nothing.
144 |
145 | ### Deprecated
146 |
147 | - Nothing.
148 |
149 | ### Removed
150 |
151 | - Nothing.
152 |
153 | ### Fixed
154 |
155 | - [#47](https://github.com/zendframework/zend-component-installer/pull/47) fixes
156 | an issue during package removal when a package defines multiple targets (e.g.,
157 | both "component" and "config-provider") and a `ConfigInjectorChain` is thus
158 | used by the plugin; previously, an error was raised due to an attempt to call
159 | a method the `ConfigInjectorChain` does not define.
160 |
161 | ## 1.1.0 - 2017-11-06
162 |
163 | ### Added
164 |
165 | - [#42](https://github.com/zendframework/zend-component-installer/pull/42)
166 | adds support for PHP 7.2.
167 |
168 | ### Deprecated
169 |
170 | - Nothing.
171 |
172 | ### Removed
173 |
174 | - [#42](https://github.com/zendframework/zend-component-installer/pull/42)
175 | removes support for HHVM.
176 |
177 | ### Fixed
178 |
179 | - [#40](https://github.com/zendframework/zend-component-installer/pull/40) and
180 | [#44](https://github.com/zendframework/zend-component-installer/pull/44) fix
181 | an issue whereby packages that define an array of paths for a PSR-0 or PSR-4
182 | autoloader would cause the installer to error. The installer now properly
183 | handles these situations.
184 |
185 | ## 1.0.0 - 2017-04-25
186 |
187 | First stable release.
188 |
189 | ### Added
190 |
191 | - Nothing.
192 |
193 | ### Deprecated
194 |
195 | - Nothing.
196 |
197 | ### Removed
198 |
199 | - Nothing.
200 |
201 | ### Fixed
202 |
203 | - Nothing.
204 |
205 | ## 0.7.1 - 2017-04-11
206 |
207 | ### Added
208 |
209 | - Nothing.
210 |
211 | ### Deprecated
212 |
213 | - Nothing.
214 |
215 | ### Removed
216 |
217 | - Nothing.
218 |
219 | ### Fixed
220 |
221 | - [#38](https://github.com/zendframework/zend-component-installer/pull/38) fixes
222 | an issue with detection of config providers in `ConfigAggregator`-based
223 | configuration files. Previously, entries that were globally qualified
224 | (prefixed with `\\`) were not properly detected, leading to the installer
225 | re-asking to inject.
226 |
227 | ## 0.7.0 - 2017-02-22
228 |
229 | ### Added
230 |
231 | - [#34](https://github.com/zendframework/zend-component-installer/pull/34) adds
232 | support for applications using [zendframework/zend-config-aggregator](https://github.com/zendframework/zend-config-aggregator).
233 |
234 | ### Changes
235 |
236 | - [#34](https://github.com/zendframework/zend-component-installer/pull/34)
237 | updates the internal architecture such that the Composer `IOInterface` no
238 | longer needs to be passed during config discovery or injection; instead,
239 | try/catch blocks are used within code exercising these classes, which already
240 | composes `IOInterface` instances. As such, a number of public methods that
241 | were receiving `IOInterface` instances now remove that argument. If you were
242 | extending any of these classes, you will need to update accordingly.
243 |
244 | ### Deprecated
245 |
246 | - Nothing.
247 |
248 | ### Removed
249 |
250 | - Nothing.
251 |
252 | ### Fixed
253 |
254 | - Nothing.
255 |
256 | ## 0.6.0 - 2017-01-09
257 |
258 | ### Added
259 |
260 | - [#31](https://github.com/zendframework/zend-component-installer/pull/31) adds
261 | support for [zend-config-aggregator](https://github.com/zendframework/zend-config-aggregator)-based
262 | application configuration.
263 |
264 | ### Deprecated
265 |
266 | - Nothing.
267 |
268 | ### Removed
269 |
270 | - Nothing.
271 |
272 | ### Fixed
273 |
274 | - Nothing.
275 |
276 | ## 0.5.1 - 2016-12-20
277 |
278 | ### Added
279 |
280 | - Nothing.
281 |
282 | ### Changes
283 |
284 | - [#29](https://github.com/zendframework/zend-component-installer/pull/29)
285 | updates the composer/composer dependency to `^1.2.2`, and, internally, uses
286 | `Composer\Installer\PackageEvent` instead of the deprecated
287 | `Composer\Script\PackageEvent`.
288 |
289 | ### Deprecated
290 |
291 | - Nothing.
292 |
293 | ### Removed
294 |
295 | - Nothing.
296 |
297 | ### Fixed
298 |
299 | - Nothing.
300 |
301 | ## 0.5.0 - 2016-10-17
302 |
303 | ### Added
304 |
305 | - [#24](https://github.com/zendframework/zend-component-installer/pull/24) adds
306 | a new method to the `InjectorInterface`: `setModuleDependencies(array $modules)`.
307 | This method is used in the `ComponentInstaller` when module dependencies are
308 | discovered, and by the injectors to provide dependency order during
309 | configuration injection.
310 |
311 | ### Deprecated
312 |
313 | - Nothing.
314 |
315 | ### Removed
316 |
317 | - Nothing.
318 |
319 | ### Fixed
320 |
321 | - [#22](https://github.com/zendframework/zend-component-installer/pull/22) and
322 | [#25](https://github.com/zendframework/zend-component-installer/pull/25) fix
323 | a bug whereby escaped namespace separators caused detection of a module in
324 | existing configuration to produce a false negative.
325 | - [#24](https://github.com/zendframework/zend-component-installer/pull/24) fixes
326 | an issue resulting from the additions from [#20](https://github.com/zendframework/zend-component-installer/pull/20)
327 | for detecting module dependencies. Since autoloading may not be setup yet, the
328 | previous approach could cause failures during installation. The patch provided
329 | in this version introduces a static analysis approach to prevent autoloading
330 | issues.
331 |
332 | ## 0.4.0 - 2016-10-11
333 |
334 | ### Added
335 |
336 | - [#12](https://github.com/zendframework/zend-component-installer/pull/12) adds
337 | a `DiscoveryChain`, for allowing discovery to use multiple discovery sources
338 | to answer the question of whether or not the application can inject
339 | configuration for the module or component. The stated use is for injection
340 | into development configuration.
341 | - [#12](https://github.com/zendframework/zend-component-installer/pull/12) adds
342 | a `ConfigInjectorChain`, which allows injecting a module or component into
343 | multiple configuration sources. The stated use is for injection into
344 | development configuration.
345 | - [#16](https://github.com/zendframework/zend-component-installer/pull/16) adds
346 | support for defining both a module and a component in the same package,
347 | ensuring that they are both injected, and at the appropriate positions in the
348 | module list.
349 | - [#20](https://github.com/zendframework/zend-component-installer/pull/20) adds
350 | support for modules that define `getModuleDependencies()`. When such a module
351 | is encountered, the installer will now also inject entries for these modules
352 | into the application module list, such that they *always* appear before the
353 | current module. This change ensures that dependencies are loaded in the
354 | correct order.
355 |
356 | ### Deprecated
357 |
358 | - Nothing.
359 |
360 | ### Removed
361 |
362 | - Nothing.
363 |
364 | ### Fixed
365 |
366 | - Nothing.
367 |
368 | ## 0.3.1 - 2016-09-12
369 |
370 | ### Added
371 |
372 | - Nothing.
373 |
374 | ### Deprecated
375 |
376 | - Nothing.
377 |
378 | ### Removed
379 |
380 | - Nothing.
381 |
382 | ### Fixed
383 |
384 | - [#15](https://github.com/zendframework/zend-component-installer/pull/15) fixes
385 | how modules are injected into configuration, ensuring they go (as documented)
386 | to the bottom of the module list, and not to the top.
387 |
388 | ## 0.3.0 - 2016-06-27
389 |
390 | ### Added
391 |
392 | - Nothing.
393 |
394 | ### Deprecated
395 |
396 | - Nothing.
397 |
398 | ### Removed
399 |
400 | - [#4](https://github.com/zendframework/zend-component-installer/pull/4) removes
401 | support for PHP 5.5.
402 |
403 | ### Fixed
404 |
405 | - [#8](https://github.com/zendframework/zend-component-installer/pull/8) fixes
406 | how the `DevelopmentConfig` discovery and injection works. Formerly, these
407 | were looking for the `development.config.php` file; however, this was
408 | incorrect. zf-development-mode has `development.config.php.dist` checked into
409 | the repository, but specifically excludes `development.config.php` from it in
410 | order to allow toggling it from the `.dist` file. The code now correctly does
411 | this.
412 |
413 | ## 0.2.0 - 2016-06-02
414 |
415 | ### Added
416 |
417 | - [#5](https://github.com/zendframework/zend-component-installer/pull/5) adds
418 | support for arrays of components/modules/config-providers, in the format:
419 |
420 | ```json
421 | {
422 | "extra": {
423 | "zf": {
424 | "component": [
425 | "Some\\Component",
426 | "Other\\Component"
427 | ]
428 | }
429 | }
430 | }
431 | ```
432 |
433 | This feature should primarily be used for metapackages, or config-providers
434 | where some configuration might not be required, and which could then be split
435 | into multiple providers.
436 |
437 | ### Deprecated
438 |
439 | - Nothing.
440 |
441 | ### Removed
442 |
443 | - Nothing.
444 |
445 | ### Fixed
446 |
447 | - Nothing.
448 |
449 | ## 0.1.0 - TBD
450 |
451 | First tagged release.
452 |
453 | Previously, PHAR releases were created from each push to the master branch.
454 | Starting in 0.1.0, the architecture changes to implement a
455 | [composer plugin](https://getcomposer.org/doc/articles/plugins.md). As such,
456 | tagged releases now make more sense, as plugins are installed via composer
457 | (either per-project or globally).
458 |
459 | ### Added
460 |
461 | - [#2](https://github.com/zendframework/zend-component-installer/pull/2) adds:
462 | - All classes in the `Zend\ComponentInstaller\ConfigDiscovery` namespace.
463 | These are used to determine which configuration files are present and
464 | injectable in the project.
465 | - All classes in the `Zend\ComponentInstaller\Injector` namespace. These are
466 | used to perform the work of injecting and removing values from configuration
467 | files.
468 | - `Zend\ComponentInstaller\ConfigOption`, a value object mapping prompt text
469 | to its related injector.
470 | - `Zend\ComponentInstaller\ConfigDiscovery`, a class that loops over known
471 | configuration discovery types to return a list of `ConfigOption` instances
472 |
473 | ### Deprecated
474 |
475 | - Nothing.
476 |
477 | ### Removed
478 |
479 | - [#2](https://github.com/zendframework/zend-component-installer/pull/2) removes
480 | all classes in the `Zend\ComponentInstaller\Command` namespace.
481 | - [#2](https://github.com/zendframework/zend-component-installer/pull/2) removes
482 | the various `bin/` scripts.
483 | - [#2](https://github.com/zendframework/zend-component-installer/pull/2) removes
484 | the PHAR distribution.
485 |
486 | ### Fixed
487 |
488 | - [#2](https://github.com/zendframework/zend-component-installer/pull/2) updates
489 | `Zend\ComponentInstaller\ComponentInstaller`:
490 | - to act as a Composer plugin.
491 | - to add awareness of additional configuration locations:
492 | - `modules.config.php` (Apigility)
493 | - `development.config.php` (zf-development-mode)
494 | - `config.php` (Expressive with expressive-config-manager)
495 | - to discover and prompt for known configuration locations when installing a
496 | package.
497 | - to allow re-using a configuration selection for remaining packages in the
498 | current install session.
499 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2019, Zend Technologies USA, Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | - Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its
15 | contributors may be used to endorse or promote products derived from this
16 | software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Component Installer for Zend Framework 3 and Expressive Applications
2 | [](https://secure.travis-ci.org/zendframework/zend-component-installer)
3 | [](https://coveralls.io/github/zendframework/zend-component-installer?branch=master)
4 |
5 | > ## Repository abandoned 2019-12-31
6 | >
7 | > This repository has moved to [laminas/laminas-component-installer](https://github.com/laminas/laminas-component-installer).
8 |
9 | This repository contains the Composer plugin class `Zend\ComponentInstaller\ComponentInstaller`,
10 | which provides Composer event hooks for the events:
11 |
12 | - post-package-install
13 | - post-package-uninstall
14 |
15 | ## Via Composer global install
16 |
17 | To install the utility for use with all projects you use:
18 |
19 | ```bash
20 | $ composer global require zendframework/zend-component-installer
21 | ```
22 |
23 | ## Per project installation
24 |
25 | To install the utility for use with a specific project already managed by
26 | composer:
27 |
28 | ```bash
29 | $ composer require zendframework/zend-component-installer
30 | ```
31 |
32 | ## Writing packages that utilize the installer
33 |
34 | Packages can opt-in to the workflow from zend-component-installer by defining
35 | one or more of the following keys under the `extra.zf` configuration in their
36 | `composer.json` file:
37 |
38 | ```json
39 | "extra": {
40 | "zf": {
41 | "component": "Component\\Namespace",
42 | "config-provider": "Classname\\For\\ConfigProvider",
43 | "module": "Module\\Namespace"
44 | }
45 | }
46 | ```
47 |
48 | - A **component** is for use specifically with zend-mvc + zend-modulemanager;
49 | a `Module` class **must** be present in the namespace associated with it.
50 | The setting indicates a low-level component that should be injected to the top
51 | of the modules list of one of:
52 | - `config/application.config.php`
53 | - `config/modules.config.php`
54 | - `config/development.config.php`
55 |
56 | - A **module** is for use specifically with zend-mvc + zend-modulemanager;
57 | a `Module` class **must** be present in the namespace associated with it.
58 | The setting indicates a userland or third-party module that should be injected
59 | to the bottom of the modules list of one of:
60 | - `config/application.config.php`
61 | - `config/modules.config.php`
62 | - `config/development.config.php`
63 |
64 | - A **config-provider** is for use with applications that utilize
65 | [expressive-config-manager](https://github.com/mtymek/expressive-config-manager)
66 | or [zend-config-aggregator](https://github.com/zendframework/zend-config-aggregator)
67 | (which may or may not be Expressive applications). The class listed must be an
68 | invokable that returns an array of configuration, and will be injected at the
69 | top of:
70 | - `config/config.php`
71 |
72 | ## Whitelisting packages to install automatically
73 |
74 | At the project level, you can mark packages that expose configuration providers
75 | and modules that you want to automatically inject via the `component-whitelist`
76 | key:
77 |
78 | ```json
79 | "extra": {
80 | "zf": {
81 | "component-whitelist": [
82 | "zendframework/zend-expressive",
83 | "zendframework/zend-expressive-helpers"
84 | ]
85 | }
86 | }
87 | ```
88 |
89 | This configuration must be made at the root package level (the package
90 | _consuming_ configuration providing packages).
91 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zendframework/zend-component-installer",
3 | "description": "Composer plugin for injecting modules and configuration providers into application configuration",
4 | "type": "composer-plugin",
5 | "license": "BSD-3-Clause",
6 | "keywords": [
7 | "zf",
8 | "zendframework",
9 | "component installer",
10 | "composer",
11 | "plugin"
12 | ],
13 | "support": {
14 | "docs": "https://docs.zendframework.com/zend-component-installer/",
15 | "issues": "https://github.com/zendframework/zend-component-installer/issues",
16 | "source": "https://github.com/zendframework/zend-component-installer",
17 | "rss": "https://github.com/zendframework/zend-component-installer/releases.atom",
18 | "chat": "https://zendframework-slack.herokuapp.com",
19 | "forum": "https://discourse.zendframework.com/c/questions/components"
20 | },
21 | "require": {
22 | "php": "^7.1",
23 | "composer-plugin-api": "^1.0"
24 | },
25 | "require-dev": {
26 | "composer/composer": "^1.5.2",
27 | "malukenho/docheader": "^0.1.6",
28 | "mikey179/vfsstream": "^1.6.7",
29 | "phpunit/phpunit": "^7.5.15 || ^8.3.4",
30 | "zendframework/zend-coding-standard": "~1.0.0"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "Zend\\ComponentInstaller\\": "src/"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "ZendTest\\ComponentInstaller\\": "test/"
40 | }
41 | },
42 | "config": {
43 | "sort-packages": true
44 | },
45 | "extra": {
46 | "branch-alias": {
47 | "dev-master": "2.1.x-dev",
48 | "dev-develop": "2.2.x-dev"
49 | },
50 | "class": "Zend\\ComponentInstaller\\ComponentInstaller"
51 | },
52 | "scripts": {
53 | "check": [
54 | "@license-check",
55 | "@cs-check",
56 | "@test"
57 | ],
58 | "cs-check": "phpcs",
59 | "cs-fix": "phpcbf",
60 | "test": "phpunit --colors=always",
61 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
62 | "license-check": "docheader check src/"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Collection.php:
--------------------------------------------------------------------------------
1 | items = $items;
52 | }
53 |
54 | /**
55 | * Factory method
56 | *
57 | * @param array|Traversable
58 | * @return static
59 | */
60 | public static function create($items)
61 | {
62 | return new static($items);
63 | }
64 |
65 | /**
66 | * Cast collection to an array.
67 | *
68 | * @return array
69 | */
70 | public function toArray()
71 | {
72 | return $this->items;
73 | }
74 |
75 | /**
76 | * Apply a callback to each item in the collection.
77 | *
78 | * @param callable $callback
79 | * @return self
80 | */
81 | public function each(callable $callback)
82 | {
83 | foreach ($this->items as $key => $item) {
84 | $callback($item, $key);
85 | }
86 | return $this;
87 | }
88 |
89 | /**
90 | * Reduce the collection to a single value.
91 | *
92 | * @param callable $callback
93 | * @param mixed $initial Initial value.
94 | * @return mixed
95 | */
96 | public function reduce(callable $callback, $initial = null)
97 | {
98 | $accumulator = $initial;
99 |
100 | foreach ($this->items as $key => $item) {
101 | $accumulator = $callback($accumulator, $item, $key);
102 | }
103 |
104 | return $accumulator;
105 | }
106 |
107 | /**
108 | * Filter the collection using a callback.
109 | *
110 | * Filter callback should return true for values to keep.
111 | *
112 | * @param callable $callback
113 | * @return static
114 | */
115 | public function filter(callable $callback)
116 | {
117 | return $this->reduce(function ($filtered, $item, $key) use ($callback) {
118 | if ($callback($item, $key)) {
119 | $filtered[$key] = $item;
120 | }
121 | return $filtered;
122 | }, new static([]));
123 | }
124 |
125 | /**
126 | * Filter the collection using a callback; reject any items matching the callback.
127 | *
128 | * Filter callback should return true for values to reject.
129 | *
130 | * @param callable $callback
131 | * @return static
132 | */
133 | public function reject(callable $callback)
134 | {
135 | return $this->reduce(function ($filtered, $item, $key) use ($callback) {
136 | if (! $callback($item, $key)) {
137 | $filtered[$key] = $item;
138 | }
139 | return $filtered;
140 | }, new static([]));
141 | }
142 |
143 | /**
144 | * Transform each value in the collection.
145 | *
146 | * Callback should return the new value to use.
147 | *
148 | * @param callable $callback
149 | * @return static
150 | */
151 | public function map(callable $callback)
152 | {
153 | return $this->reduce(function ($results, $item, $key) use ($callback) {
154 | $results[$key] = $callback($item, $key);
155 | return $results;
156 | }, new static([]));
157 | }
158 |
159 | /**
160 | * Return a new collection containing only unique items.
161 | *
162 | * @return static
163 | */
164 | public function unique()
165 | {
166 | return new static(array_unique($this->items));
167 | }
168 |
169 | /**
170 | * Merge an array of values with the current collection.
171 | *
172 | * @param array $values
173 | * @return Collection
174 | */
175 | public function merge(array $values)
176 | {
177 | $this->items = array_merge($this->items, $values);
178 | return $this;
179 | }
180 |
181 | /**
182 | * Prepend a value to the collection.
183 | *
184 | * @param mixed $value
185 | * @return Collection
186 | */
187 | public function prepend($value)
188 | {
189 | array_unshift($this->items, $value);
190 | return $this;
191 | }
192 |
193 | /**
194 | * ArrayAccess: isset()
195 | *
196 | * @param string|int $offset
197 | * @return bool
198 | */
199 | public function offsetExists($offset)
200 | {
201 | return array_key_exists($offset, $this->items);
202 | }
203 |
204 | /**
205 | * ArrayAccess: retrieve by key
206 | *
207 | * @param string|int $offset
208 | * @return mixed
209 | * @throws OutOfRangeException
210 | */
211 | public function offsetGet($offset)
212 | {
213 | if (! $this->offsetExists($offset)) {
214 | throw new OutOfRangeException(sprintf(
215 | 'Offset %s does not exist in the collection',
216 | $offset
217 | ));
218 | }
219 |
220 | return $this->items[$offset];
221 | }
222 |
223 | /**
224 | * ArrayAccess: set by key
225 | *
226 | * If $offset is null, pushes the item onto the stack.
227 | *
228 | * @param string|int $offset
229 | * @param mixed $value
230 | * @return void
231 | */
232 | public function offsetSet($offset, $value)
233 | {
234 | if (null === $offset) {
235 | $this->items[] = $value;
236 | return;
237 | }
238 |
239 | $this->items[$offset] = $value;
240 | }
241 |
242 | /**
243 | * ArrayAccess: unset()
244 | *
245 | * @param string|int $offset
246 | * @return void
247 | */
248 | public function offsetUnset($offset)
249 | {
250 | if ($this->offsetExists($offset)) {
251 | unset($this->items[$offset]);
252 | }
253 | }
254 |
255 | /**
256 | * Countable: number of items in the collection.
257 | *
258 | * @return int
259 | */
260 | public function count()
261 | {
262 | return count($this->items);
263 | }
264 |
265 | /**
266 | * Is the collection empty?
267 | *
268 | * @return bool
269 | */
270 | public function isEmpty()
271 | {
272 | return 0 === $this->count();
273 | }
274 |
275 | /**
276 | * Traversable: Iterate the collection.
277 | *
278 | * @return ArrayIterator
279 | */
280 | public function getIterator()
281 | {
282 | return new ArrayIterator($this->items);
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/src/ComponentInstaller.php:
--------------------------------------------------------------------------------
1 |
59 | * {
60 | * "extra": {
61 | * "zf": {
62 | * "component": "Zend\\Form",
63 | * "module": "ZF\\Apigility\\ContentNegotiation",
64 | * "config-provider": "Zend\\Expressive\\PlatesRenderer\\ConfigProvider"
65 | * }
66 | * }
67 | * }
68 | *
69 | *
70 | * With regards to components and modules, for this to work correctly, the
71 | * package MUST define a `Module` in the namespace listed in either the
72 | * extra.zf.component or extra.zf.module definition.
73 | *
74 | * Components are added to the TOP of the modules list, to ensure that userland
75 | * code and/or modules can override the settings. Modules are added to the
76 | * BOTTOM of the modules list. Config providers are added to the TOP of
77 | * configuration providers.
78 | *
79 | * In either case, you can edit the appropriate configuration file when
80 | * complete to create a specific order.
81 | */
82 | class ComponentInstaller implements
83 | EventSubscriberInterface,
84 | PluginInterface
85 | {
86 | /**
87 | * Cached injectors to re-use for packages installed later in the current process.
88 | *
89 | * @var Injector\InjectorInterface[]
90 | */
91 | private $cachedInjectors = [];
92 |
93 | /**
94 | * @var Composer
95 | */
96 | private $composer;
97 |
98 | /**
99 | * @var IOInterface
100 | */
101 | private $io;
102 |
103 | /**
104 | * Map of known package types to composer config keys.
105 | *
106 | * @var string[]
107 | */
108 | private $packageTypes = [
109 | Injector\InjectorInterface::TYPE_CONFIG_PROVIDER => 'config-provider',
110 | Injector\InjectorInterface::TYPE_COMPONENT => 'component',
111 | Injector\InjectorInterface::TYPE_MODULE => 'module',
112 | ];
113 |
114 | /**
115 | * Project root in which to install.
116 | *
117 | * @var string
118 | */
119 | private $projectRoot;
120 |
121 | /**
122 | * Constructor
123 | *
124 | * Optionally accept the project root into which to install.
125 | *
126 | * @param string $projectRoot
127 | */
128 | public function __construct($projectRoot = '')
129 | {
130 | if (is_string($projectRoot) && ! empty($projectRoot) && is_dir($projectRoot)) {
131 | $this->projectRoot = $projectRoot;
132 | }
133 | }
134 |
135 | /**
136 | * Activate plugin.
137 | *
138 | * Sets internal pointers to Composer and IOInterface instances, and resets
139 | * cached injector map.
140 | *
141 | * @param Composer $composer
142 | * @param IOInterface $io
143 | * @return void
144 | */
145 | public function activate(Composer $composer, IOInterface $io)
146 | {
147 | $this->composer = $composer;
148 | $this->io = $io;
149 | $this->cachedInjectors = [];
150 | }
151 |
152 | /**
153 | * Return list of event handlers in this class.
154 | *
155 | * @return string[]
156 | */
157 | public static function getSubscribedEvents()
158 | {
159 | return [
160 | 'post-package-install' => 'onPostPackageInstall',
161 | 'post-package-uninstall' => 'onPostPackageUninstall',
162 | ];
163 | }
164 |
165 | /**
166 | * post-package-install event hook.
167 | *
168 | * This routine exits early if any of the following conditions apply:
169 | *
170 | * - Executed in non-development mode
171 | * - No config/application.config.php is available
172 | * - The composer.json does not define one of either extra.zf.component
173 | * or extra.zf.module
174 | * - The value used for either extra.zf.component or extra.zf.module are
175 | * empty or not strings.
176 | *
177 | * Otherwise, it will attempt to update the application configuration
178 | * using the value(s) discovered in extra.zf.component and/or extra.zf.module,
179 | * writing their values into the `modules` list.
180 | *
181 | * @param PackageEvent $event
182 | * @return void
183 | */
184 | public function onPostPackageInstall(PackageEvent $event)
185 | {
186 | if (! $event->isDevMode()) {
187 | // Do nothing in production mode.
188 | return;
189 | }
190 |
191 | $package = $event->getOperation()->getPackage();
192 | $name = $package->getName();
193 | $extra = $this->getExtraMetadata($package->getExtra());
194 |
195 | if (empty($extra)) {
196 | // Package does not define anything of interest; do nothing.
197 | return;
198 | }
199 |
200 | $packageTypes = $this->discoverPackageTypes($extra);
201 | $options = (new ConfigDiscovery())
202 | ->getAvailableConfigOptions($packageTypes, $this->projectRoot);
203 |
204 | if ($options->isEmpty()) {
205 | // No configuration options found; do nothing.
206 | return;
207 | }
208 |
209 | $dependencies = $this->loadModuleClassesDependencies($package);
210 | $applicationModules = $this->findApplicationModules();
211 |
212 | $this->marshalInstallableModules($extra, $options)
213 | ->each(function ($module) use ($name) {
214 | })
215 | // Create injectors
216 | ->reduce(function ($injectors, $module) use ($options, $packageTypes, $name) {
217 | // Get extra from root package
218 | $rootExtra = $this->getExtraMetadata($this->composer->getPackage()->getExtra());
219 | $whitelist = $rootExtra['component-whitelist'] ?? [];
220 | $packageType = $packageTypes[$module];
221 | $injectors[$module] = $this->promptForConfigOption($module, $options, $packageType, $name, $whitelist);
222 | return $injectors;
223 | }, new Collection([]))
224 | // Inject modules into configuration
225 | ->each(function ($injector, $module) use ($name, $packageTypes, $applicationModules, $dependencies) {
226 | if (isset($dependencies[$module])) {
227 | $injector->setModuleDependencies($dependencies[$module]);
228 | }
229 |
230 | $injector->setApplicationModules($applicationModules);
231 | $this->injectModuleIntoConfig($name, $module, $injector, $packageTypes[$module]);
232 | });
233 | }
234 |
235 | /**
236 | * Find all Module classes in the package and their dependencies
237 | * via method `getModuleDependencies` of Module class.
238 | *
239 | * These dependencies are used later
240 | * @see \Zend\ComponentInstaller\Injector\AbstractInjector::injectAfterDependencies
241 | * to add component in a correct order on the module list - after dependencies.
242 | *
243 | * It works with PSR-0, PSR-4, 'classmap' and 'files' composer autoloading.
244 | *
245 | * @param PackageInterface $package
246 | * @return array
247 | */
248 | private function loadModuleClassesDependencies(PackageInterface $package)
249 | {
250 | $dependencies = new ArrayObject([]);
251 | $installer = $this->composer->getInstallationManager();
252 | $packagePath = $installer->getInstallPath($package);
253 |
254 | $this->mapAutoloaders($package->getAutoload(), $dependencies, $packagePath);
255 |
256 | return $dependencies->getArrayCopy();
257 | }
258 |
259 | /**
260 | * Find all modules of the application.
261 | *
262 | * @return array
263 | */
264 | private function findApplicationModules()
265 | {
266 | $modulePath = is_string($this->projectRoot) && ! empty($this->projectRoot)
267 | ? sprintf('%s/module', $this->projectRoot)
268 | : 'module';
269 |
270 | $modules = [];
271 |
272 | if (is_dir($modulePath)) {
273 | $directoryIterator = new DirectoryIterator($modulePath);
274 | foreach ($directoryIterator as $file) {
275 | if ($file->isDot() || ! $file->isDir()) {
276 | continue;
277 | }
278 |
279 | $modules[] = $file->getBasename();
280 | }
281 | }
282 |
283 | return $modules;
284 | }
285 |
286 | /**
287 | * post-package-uninstall event hook
288 | *
289 | * This routine exits early if any of the following conditions apply:
290 | *
291 | * - Executed in non-development mode
292 | * - No config/application.config.php is available
293 | * - The composer.json does not define one of either extra.zf.component
294 | * or extra.zf.module
295 | * - The value used for either extra.zf.component or extra.zf.module are
296 | * empty or not strings.
297 | *
298 | * Otherwise, it will attempt to update the application configuration
299 | * using the value(s) discovered in extra.zf.component and/or extra.zf.module,
300 | * removing their values from the `modules` list.
301 | *
302 | * @param PackageEvent $event
303 | * @return void
304 | */
305 | public function onPostPackageUninstall(PackageEvent $event)
306 | {
307 | if (! $event->isDevMode()) {
308 | // Do nothing in production mode.
309 | return;
310 | }
311 |
312 | $options = (new ConfigDiscovery())
313 | ->getAvailableConfigOptions(
314 | new Collection(array_keys($this->packageTypes)),
315 | $this->projectRoot
316 | );
317 |
318 | if ($options->isEmpty()) {
319 | // No configuration options found; do nothing.
320 | return;
321 | }
322 |
323 | $package = $event->getOperation()->getPackage();
324 | $name = $package->getName();
325 | $extra = $this->getExtraMetadata($package->getExtra());
326 | $this->removePackageFromConfig($name, $extra, $options);
327 | }
328 |
329 | /**
330 | * Retrieve the zf-specific metadata from the "extra" section
331 | *
332 | * @param array $extra
333 | * @return array
334 | */
335 | private function getExtraMetadata(array $extra)
336 | {
337 | return isset($extra['zf']) && is_array($extra['zf'])
338 | ? $extra['zf']
339 | : []
340 | ;
341 | }
342 |
343 | /**
344 | * Discover what package types are relevant based on what the package
345 | * exposes in the extra configuration.
346 | *
347 | * @param string[] $extra
348 | * @return Collection Collection of Injector\InjectorInterface::TYPE_* constants.
349 | */
350 | private function discoverPackageTypes(array $extra)
351 | {
352 | $packageTypes = array_flip($this->packageTypes);
353 | $knownTypes = array_keys($packageTypes);
354 | return Collection::create($extra)
355 | ->filter(function ($packages, $type) use ($knownTypes) {
356 | return in_array($type, $knownTypes, true);
357 | })
358 | ->reduce(function ($discoveredTypes, $packages, $type) use ($packageTypes) {
359 | $packages = is_array($packages) ? $packages : [$packages];
360 |
361 | foreach ($packages as $package) {
362 | $discoveredTypes[$package] = $packageTypes[$type];
363 | }
364 | return $discoveredTypes;
365 | }, new Collection([]));
366 | }
367 |
368 | /**
369 | * Marshal a collection of defined package types.
370 | *
371 | * @param array $extra extra.zf value
372 | * @return Collection
373 | */
374 | private function marshalPackageTypes(array $extra)
375 | {
376 | // Create a collection of types registered in the package.
377 | return Collection::create($this->packageTypes)
378 | ->filter(function ($configKey, $type) use ($extra) {
379 | return $this->metadataForKeyIsValid($configKey, $extra);
380 | });
381 | }
382 |
383 | /**
384 | * Marshal a collection of package modules.
385 | *
386 | * @param array $extra extra.zf value
387 | * @param Collection $packageTypes
388 | * @param Collection $options ConfigOption instances
389 | * @return Collection
390 | */
391 | private function marshalPackageModules(array $extra, Collection $packageTypes, Collection $options)
392 | {
393 | // We only want to list modules that the application can configure.
394 | $supportedTypes = $options
395 | ->reduce(function ($allowed, $option) {
396 | return $allowed->merge($option->getInjector()->getTypesAllowed());
397 | }, new Collection([]))
398 | ->unique()
399 | ->toArray();
400 |
401 | return $packageTypes
402 | ->reduce(function ($modules, $configKey, $type) use ($extra, $supportedTypes) {
403 | if (! in_array($type, $supportedTypes, true)) {
404 | return $modules;
405 | }
406 | return $modules->merge((array) $extra[$configKey]);
407 | }, new Collection([]))
408 | // Make sure the list is unique
409 | ->unique();
410 | }
411 |
412 | /**
413 | * Prepare a list of modules to install/register with configuration.
414 | *
415 | * @param string[] $extra
416 | * @param Collection $options
417 | * @return string[] List of packages to install
418 | */
419 | private function marshalInstallableModules(array $extra, Collection $options)
420 | {
421 | return $this->marshalPackageModules($extra, $this->marshalPackageTypes($extra), $options)
422 | // Filter out modules that do not have a registered injector
423 | ->reject(function ($module) use ($options) {
424 | return $options->reduce(function ($registered, $option) use ($module) {
425 | return $registered || $option->getInjector()->isRegistered($module);
426 | }, false);
427 | });
428 | }
429 |
430 | /**
431 | * Prompt for the user to select a configuration location to update.
432 | *
433 | * @param string $name
434 | * @param Collection $options
435 | * @param int $packageType
436 | * @param string $packageName
437 | * @param array $whitelist
438 | * @return Injector\InjectorInterface
439 | */
440 | private function promptForConfigOption(
441 | string $name,
442 | Collection $options,
443 | int $packageType,
444 | string $packageName,
445 | array $whitelist
446 | ) {
447 | if ($cachedInjector = $this->getCachedInjector($packageType)) {
448 | return $cachedInjector;
449 | }
450 |
451 | // If package is whitelisted, don't ask...
452 | if (in_array($packageName, $whitelist, true)) {
453 | return $options[1]->getInjector();
454 | }
455 |
456 | // Default to first discovered option; index 0 is always "Do not inject"
457 | $default = $options->count() > 1 ? 1 : 0;
458 | $ask = $options->reduce(function ($ask, $option, $index) {
459 | $ask[] = sprintf(
460 | " [%d] %s\n",
461 | $index,
462 | $option->getPromptText()
463 | );
464 | return $ask;
465 | }, []);
466 |
467 | array_unshift($ask, sprintf(
468 | "\n Please select which config file you wish to inject '%s' into:\n",
469 | $name
470 | ));
471 | $ask[] = sprintf(' Make your selection (default is %d):', $default);
472 |
473 | while (true) {
474 | $answer = $this->io->ask(implode($ask), $default);
475 |
476 | if (is_numeric($answer) && isset($options[(int) $answer])) {
477 | $injector = $options[(int) $answer]->getInjector();
478 | $this->promptToRememberOption($injector, $packageType);
479 | return $injector;
480 | }
481 |
482 | $this->io->write('Invalid selection');
483 | }
484 | }
485 |
486 | /**
487 | * Prompt the user to determine if the selection should be remembered for later packages.
488 | *
489 | * @todo Will need to store selection in filesystem and remove when all packages are complete
490 | * @param Injector\InjectorInterface $injector
491 | * @param int $packageType
492 | * return void
493 | */
494 | private function promptToRememberOption(Injector\InjectorInterface $injector, $packageType)
495 | {
496 | $ask = ["\n Remember this option for other packages of the same type? (Y/n)"];
497 |
498 | while (true) {
499 | $answer = strtolower($this->io->ask(implode($ask), 'y'));
500 |
501 | switch ($answer) {
502 | case 'y':
503 | $this->cacheInjector($injector, $packageType);
504 | return;
505 | case 'n':
506 | // intentionally fall-through
507 | default:
508 | return;
509 | }
510 | }
511 | }
512 |
513 | /**
514 | * Inject a module into available configuration.
515 | *
516 | * @param string $package Package name
517 | * @param string $module Module to install in configuration
518 | * @param Injector\InjectorInterface $injector Injector to use.
519 | * @param int $packageType
520 | * @return void
521 | */
522 | private function injectModuleIntoConfig($package, $module, Injector\InjectorInterface $injector, $packageType)
523 | {
524 | $this->io->write(sprintf(' Installing %s from package %s', $module, $package));
525 |
526 | try {
527 | if (! $injector->inject($module, $packageType)) {
528 | $this->io->write(' Package is already registered; skipping');
529 | }
530 | } catch (Exception\RuntimeException $ex) {
531 | $this->io->write(sprintf(
532 | ' %s',
533 | $ex->getMessage()
534 | ));
535 | }
536 | }
537 |
538 | /**
539 | * Remove a package from configuration.
540 | *
541 | * @param string $package Package name
542 | * @param array $metadata Metadata pulled from extra.zf
543 | * @param Collection $configOptions Discovered configuration options from
544 | * which to remove package.
545 | * @return void
546 | */
547 | private function removePackageFromConfig($package, array $metadata, Collection $configOptions)
548 | {
549 | // Create a collection of types registered in the package.
550 | $packageTypes = $this->marshalPackageTypes($metadata);
551 |
552 | // Create a collection of configured injectors for the package types
553 | // registered.
554 | $injectors = $configOptions
555 | ->map(function ($configOption) {
556 | return $configOption->getInjector();
557 | })
558 | ->filter(function ($injector) use ($packageTypes) {
559 | return $packageTypes->reduce(function ($registered, $key, $type) use ($injector) {
560 | return $registered || $injector->registersType($type);
561 | }, false);
562 | });
563 |
564 | // Create a collection of unique modules based on the package types present,
565 | // and remove each from configuration.
566 | $this->marshalPackageModules($metadata, $packageTypes, $configOptions)
567 | ->each(function ($module) use ($package, $injectors) {
568 | $this->removeModuleFromConfig($module, $package, $injectors);
569 | });
570 | }
571 |
572 | /**
573 | * Remove an individual module defined in a package from configuration.
574 | *
575 | * @param string $module Module to remove
576 | * @param string $package Package in which module is defined
577 | * @param Collection $injectors Injectors to use for removal
578 | * @return void
579 | */
580 | private function removeModuleFromConfig($module, $package, Collection $injectors)
581 | {
582 | $injectors->each(function (InjectorInterface $injector) use ($module, $package) {
583 | $this->io->write(sprintf(' Removing %s from package %s', $module, $package));
584 |
585 | if ($injector->remove($module)) {
586 | $this->io->write(sprintf(
587 | ' Removed package from %s',
588 | $this->getInjectorConfigFileName($injector)
589 | ));
590 | }
591 | });
592 | }
593 |
594 | /**
595 | * @param InjectorInterface $injector
596 | * @return string
597 | * @todo remove after InjectorInterface has getConfigName defined
598 | */
599 | private function getInjectorConfigFileName(InjectorInterface $injector)
600 | {
601 | if ($injector instanceof ConfigInjectorChain) {
602 | return $this->getInjectorChainConfigFileName($injector);
603 | } elseif ($injector instanceof AbstractInjector) {
604 | return $this->getAbstractInjectorConfigFileName($injector);
605 | }
606 |
607 | return '';
608 | }
609 |
610 | /**
611 | * @param ConfigInjectorChain $injector
612 | * @return string
613 | * @todo remove after InjectorInterface has getConfigName defined
614 | */
615 | private function getInjectorChainConfigFileName(ConfigInjectorChain $injector)
616 | {
617 | return implode(', ', array_map(function ($item) {
618 | return $this->getInjectorConfigFileName($item);
619 | }, $injector->getCollection()->toArray()));
620 | }
621 |
622 | /**
623 | * @param AbstractInjector $injector
624 | * @return string
625 | * @todo remove after InjectorInterface has getConfigName defined
626 | */
627 | private function getAbstractInjectorConfigFileName(AbstractInjector $injector)
628 | {
629 | return $injector->getConfigFile();
630 | }
631 |
632 | /**
633 | * Is a given module name valid?
634 | *
635 | * @param string $module
636 | * @return bool
637 | */
638 | private function moduleIsValid($module)
639 | {
640 | return is_string($module) && ! empty($module);
641 | }
642 |
643 | /**
644 | * Is a given metadata value (extra.zf.*) valid?
645 | *
646 | * @param string $key Key to examine in metadata
647 | * @param array $metadata
648 | * @return bool
649 | */
650 | private function metadataForKeyIsValid($key, array $metadata)
651 | {
652 | if (! isset($metadata[$key])) {
653 | return false;
654 | }
655 |
656 | if (is_string($metadata[$key])) {
657 | return $this->moduleIsValid($metadata[$key]);
658 | }
659 |
660 | if (! is_array($metadata[$key])) {
661 | return false;
662 | }
663 |
664 | return Collection::create($metadata[$key])
665 | ->reduce(function ($valid, $value) {
666 | if (false === $valid) {
667 | return $valid;
668 | }
669 | return $this->moduleIsValid($value);
670 | }, null);
671 | }
672 |
673 | /**
674 | * Attempt to retrieve a cached injector for the current package type.
675 | *
676 | * @param int $packageType
677 | * @return null|Injector\InjectorInterface
678 | */
679 | private function getCachedInjector($packageType)
680 | {
681 | if (isset($this->cachedInjectors[$packageType])) {
682 | return $this->cachedInjectors[$packageType];
683 | }
684 |
685 | return null;
686 | }
687 |
688 | /**
689 | * Cache an injector for later use.
690 | *
691 | * @param Injector\InjectorInterface $injector
692 | * @param int $packageType
693 | * @return void
694 | */
695 | private function cacheInjector(Injector\InjectorInterface $injector, $packageType)
696 | {
697 | $this->cachedInjectors[$packageType] = $injector;
698 | }
699 |
700 | /**
701 | * Iterate through each autoloader type to find dependencies.
702 | *
703 | * @param array $autoload List of autoloader types and associated autoloader definitions.
704 | * @param ArrayObject $dependencies Module dependencies defined by the module.
705 | * @param string $packagePath Path to the package on the filesystem.
706 | * @return void
707 | */
708 | private function mapAutoloaders(array $autoload, ArrayObject $dependencies, $packagePath)
709 | {
710 | foreach ($autoload as $type => $map) {
711 | $this->mapType($map, $type, $dependencies, $packagePath);
712 | }
713 | }
714 |
715 | /**
716 | * Iterate through a single autolaoder type to find dependencies.
717 | *
718 | * @param array $map Map of namespace => path(s) pairs.
719 | * @param string $type Type of autoloader being iterated.
720 | * @param ArrayObject $dependencies Module dependencies defined by the module.
721 | * @param string $packagePath Path to the package on the filesystem.
722 | * @return void
723 | */
724 | private function mapType(array $map, $type, ArrayObject $dependencies, $packagePath)
725 | {
726 | foreach ($map as $namespace => $paths) {
727 | $paths = (array) $paths;
728 | $this->mapNamespacePaths($paths, $namespace, $type, $dependencies, $packagePath);
729 | }
730 | }
731 |
732 | /**
733 | * Iterate through the paths defined for a given namespace.
734 | *
735 | * @param array $paths Paths defined for the given namespace.
736 | * @param string $namespace PHP namespace to which the paths map.
737 | * @param string $type Type of autoloader being iterated.
738 | * @param ArrayObject $dependencies Module dependencies defined by the module.
739 | * @param string $packagePath Path to the package on the filesystem.
740 | * @return void
741 | */
742 | private function mapNamespacePaths(array $paths, $namespace, $type, ArrayObject $dependencies, $packagePath)
743 | {
744 | foreach ($paths as $path) {
745 | $this->mapPath($path, $namespace, $type, $dependencies, $packagePath);
746 | }
747 | }
748 |
749 | /**
750 | * Find module dependencies for a given namespace for a given path.
751 | *
752 | * @param string $path Path to inspect.
753 | * @param string $namespace PHP namespace to which the paths map.
754 | * @param string $type Type of autoloader being iterated.
755 | * @param ArrayObject $dependencies Module dependencies defined by the module.
756 | * @param string $packagePath Path to the package on the filesystem.
757 | * @return void
758 | */
759 | private function mapPath($path, $namespace, $type, ArrayObject $dependencies, $packagePath)
760 | {
761 | switch ($type) {
762 | case 'classmap':
763 | $fullPath = sprintf('%s/%s', $packagePath, $path);
764 | if (substr($path, -10) === 'Module.php') {
765 | $modulePath = $fullPath;
766 | break;
767 | }
768 |
769 | $modulePath = sprintf('%s/Module.php', rtrim($fullPath, '/'));
770 | break;
771 | case 'files':
772 | if (substr($path, -10) !== 'Module.php') {
773 | return;
774 | }
775 | $modulePath = sprintf('%s/%s', $packagePath, $path);
776 | break;
777 | case 'psr-0':
778 | $modulePath = sprintf(
779 | '%s/%s%s%s',
780 | $packagePath,
781 | $path,
782 | str_replace('\\', '/', $namespace),
783 | 'Module.php'
784 | );
785 | break;
786 | case 'psr-4':
787 | $modulePath = sprintf(
788 | '%s/%s%s',
789 | $packagePath,
790 | $path,
791 | 'Module.php'
792 | );
793 | break;
794 | default:
795 | return;
796 | }
797 |
798 | if (! file_exists($modulePath)) {
799 | return;
800 | }
801 |
802 | $result = $this->getModuleDependencies($modulePath);
803 |
804 | if (empty($result)) {
805 | return;
806 | }
807 |
808 | // Mimic array + array operation in ArrayObject
809 | $dependencies->exchangeArray($dependencies->getArrayCopy() + $result);
810 | }
811 |
812 | /**
813 | * @param string $file
814 | * @return array
815 | */
816 | private function getModuleDependencies($file)
817 | {
818 | $content = file_get_contents($file);
819 | if (preg_match('/namespace\s+([^\s]+)\s*;/', $content, $m)) {
820 | $moduleName = $m[1];
821 |
822 | // @codingStandardsIgnoreStart
823 | $regExp = '/public\s+function\s+getModuleDependencies\s*\(\s*\)\s*{[^}]*return\s*(?:array\(|\[)([^})\]]*)(\)|\])/';
824 | // @codingStandardsIgnoreEnd
825 | if (preg_match($regExp, $content, $m)) {
826 | $dependencies = array_filter(
827 | explode(',', stripslashes(rtrim(preg_replace('/[\s"\']/', '', $m[1]), ',')))
828 | );
829 |
830 | if ($dependencies) {
831 | return [$moduleName => $dependencies];
832 | }
833 | }
834 | }
835 |
836 | return [];
837 | }
838 | }
839 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery.php:
--------------------------------------------------------------------------------
1 | ConfigDiscovery\ApplicationConfig::class,
21 | 'config/modules.config.php' => ConfigDiscovery\ModulesConfig::class,
22 | 'config/development.config.php.dist' => [
23 | 'dist' => ConfigDiscovery\DevelopmentConfig::class,
24 | 'work' => ConfigDiscovery\DevelopmentWorkConfig::class,
25 | ],
26 | 'config/config.php' => [
27 | 'aggregator' => ConfigDiscovery\ConfigAggregator::class,
28 | 'manager' => ConfigDiscovery\ExpressiveConfig::class,
29 | ],
30 | ];
31 |
32 | /**
33 | * Map of config files to injectors
34 | *
35 | * @var string[]
36 | */
37 | private $injectors = [
38 | 'config/application.config.php' => Injector\ApplicationConfigInjector::class,
39 | 'config/modules.config.php' => Injector\ModulesConfigInjector::class,
40 | 'config/development.config.php.dist' => [
41 | 'dist' => Injector\DevelopmentConfigInjector::class,
42 | 'work' => Injector\DevelopmentWorkConfigInjector::class,
43 | ],
44 | 'config/config.php' => [
45 | 'aggregator' => Injector\ConfigAggregatorInjector::class,
46 | 'manager' => Injector\ExpressiveConfigInjector::class,
47 | ]
48 | ];
49 |
50 | /**
51 | * Return a list of available configuration options.
52 | *
53 | * @param Collection $availableTypes Collection of Injector\InjectorInterface::TYPE_*
54 | * constants indicating valid package types that could be injected.
55 | * @param string $projectRoot Path to the project root; assumes PWD by
56 | * default.
57 | * @return Collection Collection of ConfigOption instances.
58 | */
59 | public function getAvailableConfigOptions(Collection $availableTypes, $projectRoot = '')
60 | {
61 | // Create an initial collection to which we'll append.
62 | // This approach is used to ensure indexes are sane.
63 | $discovered = new Collection([
64 | new ConfigOption('Do not inject', new Injector\NoopInjector()),
65 | ]);
66 |
67 | Collection::create($this->discovery)
68 | // Create a discovery class for the discovery type
69 | ->map(function ($discoveryClass) use ($projectRoot) {
70 | if (is_array($discoveryClass)) {
71 | return new ConfigDiscovery\DiscoveryChain($discoveryClass, $projectRoot);
72 | }
73 | return new $discoveryClass($projectRoot);
74 | })
75 | // Use only those where we can locate a corresponding config file
76 | ->filter(function ($discovery) {
77 | return $discovery->locate();
78 | })
79 | // Create an injector for the config file
80 | ->map(function ($discovery, $file) use ($projectRoot, $availableTypes) {
81 | // Look up the injector based on the file type
82 | $injectorClass = $this->injectors[$file];
83 | if (is_array($injectorClass)) {
84 | return new Injector\ConfigInjectorChain(
85 | $injectorClass,
86 | $discovery,
87 | $availableTypes,
88 | $projectRoot
89 | );
90 | }
91 | return new $injectorClass($projectRoot);
92 | })
93 | // Keep only those injectors that match types available for the package
94 | ->filter(function ($injector) use ($availableTypes) {
95 | return $availableTypes->reduce(function ($flag, $type) use ($injector) {
96 | return $flag || $injector->registersType($type);
97 | }, false);
98 | })
99 | // Create a config option using the file and injector
100 | ->each(function ($injector, $file) use ($discovered) {
101 | $discovered[] = new ConfigOption($file, $injector);
102 | });
103 |
104 | return 1 === $discovered->count()
105 | ? new Collection([])
106 | : $discovered;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/AbstractDiscovery.php:
--------------------------------------------------------------------------------
1 | configFile = sprintf(
48 | '%s/%s',
49 | $projectDirectory,
50 | $this->configFile
51 | );
52 | }
53 | }
54 |
55 | /**
56 | * Determine if the configuration file exists and contains modules.
57 | *
58 | * @return bool
59 | */
60 | public function locate()
61 | {
62 | if (! is_file($this->configFile)) {
63 | return false;
64 | }
65 |
66 | $config = file_get_contents($this->configFile);
67 | return (1 === preg_match($this->expected, $config));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/ApplicationConfig.php:
--------------------------------------------------------------------------------
1 | \s*(array\(|\[))\s*$/m';
25 | }
26 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/ConfigAggregator.php:
--------------------------------------------------------------------------------
1 | expected = sprintf(
34 | '/new (?:%s?%s)?ConfigAggregator\(\s*(?:array\(|\[)/s',
35 | preg_quote('\\'),
36 | preg_quote('Zend\ConfigAggregator\\')
37 | );
38 |
39 | parent::__construct($projectDirectory);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/DevelopmentConfig.php:
--------------------------------------------------------------------------------
1 | chain = Collection::create($discovery)
33 | // Create a discovery class for the dicovery type
34 | ->map(function ($discoveryClass) use ($projectDirectory) {
35 | return new $discoveryClass($projectDirectory);
36 | })
37 | // Use only those where we can locate a corresponding config file
38 | ->filter(function ($discovery) {
39 | return $discovery->locate();
40 | });
41 | }
42 |
43 | /**
44 | * {@inheritDoc}
45 | */
46 | public function locate()
47 | {
48 | return $this->chain->count() > 0;
49 | }
50 |
51 | /**
52 | * {@inheritDoc}
53 | */
54 | public function discoveryExists($name)
55 | {
56 | return $this->chain->offsetExists($name);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/DiscoveryChainInterface.php:
--------------------------------------------------------------------------------
1 | expected = sprintf(
34 | '/new (?:%s?%s)?ConfigManager\(\s*(?:array\(|\[)/s',
35 | preg_quote('\\'),
36 | preg_quote('Zend\Expressive\ConfigManager\\')
37 | );
38 |
39 | parent::__construct($projectDirectory);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ConfigDiscovery/ModulesConfig.php:
--------------------------------------------------------------------------------
1 | promptText = $promptText;
29 | $this->injector = $injector;
30 | }
31 |
32 | /**
33 | * @return string
34 | */
35 | public function getPromptText()
36 | {
37 | return $this->promptText;
38 | }
39 |
40 | /**
41 | * @return Injector\InjectorInterface
42 | */
43 | public function getInjector()
44 | {
45 | return $this->injector;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
1 | 'regular expression',
51 | * 'replacement' => 'preg_replace replacement',
52 | * ],
53 | * ```
54 | *
55 | * @var string[]
56 | */
57 | protected $cleanUpPatterns = [
58 | 'pattern' => "/(array\(|\[|,)(\r?\n){2}/s",
59 | 'replacement' => "\$1\n",
60 | ];
61 |
62 | /**
63 | * Configuration file to update.
64 | *
65 | * Implementations MUST overwrite this value.
66 | *
67 | * @var string
68 | */
69 | protected $configFile;
70 |
71 | /**
72 | * Patterns and replacements to use when registering a code item.
73 | *
74 | * Implementations MUST overwrite this value.
75 | *
76 | * Structure MUST be:
77 | *
78 | * ```
79 | * [
80 | * TYPE_CONSTANT => [
81 | * 'pattern' => 'regular expression',
82 | * 'replacement' => 'preg_replace replacement, with %s placeholder for package',
83 | * ],
84 | * ]
85 | * ```
86 | *
87 | * @var string[]
88 | */
89 | protected $injectionPatterns = [];
90 |
91 | /**
92 | * Pattern to use to determine if the code item is registered.
93 | *
94 | * Implementations MUST overwrite this value.
95 | *
96 | * @var string
97 | */
98 | protected $isRegisteredPattern;
99 |
100 | /**
101 | * Patterns and replacements to use when removing a code item.
102 | *
103 | * Implementations MUST overwrite this value.
104 | *
105 | * Structure MUST be:
106 | *
107 | * ```
108 | * [
109 | * 'pattern' => 'regular expression, with %s placeholder for component namespace/configuration class',
110 | * 'replacement' => 'preg_replace replacement, usually an empty string',
111 | * ],
112 | * ```
113 | *
114 | * @var string[]
115 | */
116 | protected $removalPatterns = [];
117 |
118 | /**
119 | * Modules of the application.
120 | *
121 | * @var array
122 | */
123 | protected $applicationModules = [];
124 |
125 | /**
126 | * Dependencies of the module.
127 | *
128 | * @var array
129 | */
130 | protected $moduleDependencies = [];
131 |
132 | /**
133 | * Constructor
134 | *
135 | * Optionally accept the project root directory; if non-empty, it is used
136 | * to prefix the $configFile.
137 | *
138 | * @param string $projectRoot
139 | */
140 | public function __construct($projectRoot = '')
141 | {
142 | if (is_string($projectRoot) && ! empty($projectRoot)) {
143 | $this->configFile = sprintf('%s/%s', $projectRoot, $this->configFile);
144 | }
145 | }
146 |
147 | /**
148 | * {@inheritDoc}
149 | */
150 | public function registersType($type)
151 | {
152 | return in_array($type, $this->allowedTypes, true);
153 | }
154 |
155 | /**
156 | * {@inheritDoc}
157 | */
158 | public function getTypesAllowed()
159 | {
160 | return $this->allowedTypes;
161 | }
162 |
163 | /**
164 | * {@inheritDoc}
165 | */
166 | public function isRegistered($package)
167 | {
168 | $config = file_get_contents($this->configFile);
169 | return $this->isRegisteredInConfig($package, $config);
170 | }
171 |
172 | /**
173 | * {@inheritDoc}
174 | */
175 | public function inject($package, $type)
176 | {
177 | $config = file_get_contents($this->configFile);
178 |
179 | if ($this->isRegisteredInConfig($package, $config)) {
180 | return false;
181 | }
182 |
183 | if ($type === self::TYPE_COMPONENT
184 | && $this->moduleDependencies
185 | ) {
186 | return $this->injectAfterDependencies($package, $config);
187 | }
188 |
189 | if ($type === self::TYPE_MODULE
190 | && ($firstApplicationModule = $this->findFirstEnabledApplicationModule($this->applicationModules, $config))
191 | ) {
192 | return $this->injectBeforeApplicationModules($package, $config, $firstApplicationModule);
193 | }
194 |
195 | $pattern = $this->injectionPatterns[$type]['pattern'];
196 | $replacement = sprintf(
197 | $this->injectionPatterns[$type]['replacement'],
198 | $package
199 | );
200 |
201 | $config = preg_replace($pattern, $replacement, $config);
202 | file_put_contents($this->configFile, $config);
203 |
204 | return true;
205 | }
206 |
207 | /**
208 | * Injects component $package into $config after all other dependencies.
209 | *
210 | * If any dependencies are not registered, the method throws
211 | * Exception\RuntimeException.
212 | *
213 | * @param string $package
214 | * @param string $config
215 | * @return true
216 | * @throws Exception\RuntimeException
217 | */
218 | private function injectAfterDependencies($package, $config)
219 | {
220 | foreach ($this->moduleDependencies as $dependency) {
221 | if (! $this->isRegisteredInConfig($dependency, $config)) {
222 | throw new Exception\RuntimeException(sprintf(
223 | 'Dependency %s is not registered in the configuration',
224 | $dependency
225 | ));
226 | }
227 | }
228 |
229 | $lastDependency = $this->findLastDependency($this->moduleDependencies, $config);
230 |
231 | $pattern = sprintf(
232 | $this->injectionPatterns[self::TYPE_DEPENDENCY]['pattern'],
233 | preg_quote($lastDependency, '/')
234 | );
235 | $replacement = sprintf(
236 | $this->injectionPatterns[self::TYPE_DEPENDENCY]['replacement'],
237 | $package
238 | );
239 |
240 | $config = preg_replace($pattern, $replacement, $config);
241 | file_put_contents($this->configFile, $config);
242 |
243 | return true;
244 | }
245 |
246 | /**
247 | * Find which of dependency packages is the last one on the module list.
248 | *
249 | * @param array $dependencies
250 | * @param string $config
251 | * @return string
252 | */
253 | private function findLastDependency(array $dependencies, $config)
254 | {
255 | if (count($dependencies) === 1) {
256 | return reset($dependencies);
257 | }
258 |
259 | $longLength = 0;
260 | $last = null;
261 | foreach ($dependencies as $dependency) {
262 | preg_match(sprintf($this->isRegisteredPattern, preg_quote($dependency, '/')), $config, $matches);
263 |
264 | $length = strlen($matches[0]);
265 | if ($length > $longLength) {
266 | $longLength = $length;
267 | $last = $dependency;
268 | }
269 | }
270 |
271 | return $last;
272 | }
273 |
274 | /**
275 | * Inject module $package into $config before the first found application module
276 | * and return true.
277 | * If there is no any enabled application module, this method will return false.
278 | *
279 | * @param string $package
280 | * @param string $config
281 | * @param string $firstApplicationModule
282 | * @return bool
283 | */
284 | private function injectBeforeApplicationModules($package, $config, $firstApplicationModule)
285 | {
286 | $pattern = sprintf(
287 | $this->injectionPatterns[self::TYPE_BEFORE_APPLICATION]['pattern'],
288 | preg_quote($firstApplicationModule, '/')
289 | );
290 | $replacement = sprintf(
291 | $this->injectionPatterns[self::TYPE_BEFORE_APPLICATION]['replacement'],
292 | $package
293 | );
294 |
295 | $config = preg_replace($pattern, $replacement, $config);
296 | file_put_contents($this->configFile, $config);
297 |
298 | return true;
299 | }
300 |
301 | /**
302 | * Find the first enabled application module from list $modules in the $config.
303 | * If any module is not found method will return null.
304 | *
305 | * @param array $modules
306 | * @param string $config
307 | * @return string|null
308 | */
309 | private function findFirstEnabledApplicationModule(array $modules, $config)
310 | {
311 | $shortest = strlen($config);
312 | $first = null;
313 | foreach ($modules as $module) {
314 | if (! $this->isRegistered($module)) {
315 | continue;
316 | }
317 |
318 | preg_match(sprintf($this->isRegisteredPattern, preg_quote($module, '/')), $config, $matches);
319 |
320 | $length = strlen($matches[0]);
321 | if ($length < $shortest) {
322 | $shortest = $length;
323 | $first = $module;
324 | }
325 | }
326 |
327 | return $first;
328 | }
329 |
330 | /**
331 | * {@inheritDoc}
332 | */
333 | public function setApplicationModules(array $modules)
334 | {
335 | $this->applicationModules = $modules;
336 |
337 | return $this;
338 | }
339 |
340 | /**
341 | * {@inheritDoc}
342 | */
343 | public function setModuleDependencies(array $modules)
344 | {
345 | $this->moduleDependencies = $modules;
346 |
347 | return $this;
348 | }
349 |
350 | /**
351 | * Removes a package from the configuration.
352 | * Returns true if successfully removed,
353 | * false when package is not registered.
354 | *
355 | * @param string $package Package name.
356 | * @return bool
357 | */
358 | public function remove($package)
359 | {
360 | $config = file_get_contents($this->configFile);
361 |
362 | if (! $this->isRegisteredInConfig($package, $config)) {
363 | return false;
364 | }
365 |
366 | $config = preg_replace(
367 | sprintf($this->removalPatterns['pattern'], preg_quote($package)),
368 | $this->removalPatterns['replacement'],
369 | $config
370 | );
371 |
372 | $config = preg_replace(
373 | $this->cleanUpPatterns['pattern'],
374 | $this->cleanUpPatterns['replacement'],
375 | $config
376 | );
377 |
378 | file_put_contents($this->configFile, $config);
379 |
380 | return true;
381 | }
382 |
383 | /**
384 | * Returns config file name of the injector.
385 | *
386 | * @return string
387 | */
388 | public function getConfigFile()
389 | {
390 | return $this->configFile;
391 | }
392 |
393 | /**
394 | * Is the code item registered in the configuration already?
395 | *
396 | * @var string $package Package name
397 | * @var string $config
398 | * @return bool
399 | */
400 | protected function isRegisteredInConfig($package, $config)
401 | {
402 | return preg_match(sprintf($this->isRegisteredPattern, preg_quote($package, '/')), $config)
403 | || preg_match(sprintf($this->isRegisteredPattern, preg_quote(addslashes($package), '/')), $config);
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/src/Injector/ApplicationConfigInjector.php:
--------------------------------------------------------------------------------
1 | [
26 | 'pattern' => '/^(\s+)(\'modules\'\s*\=\>\s*(?:array\s*\(|\[))\s*$/m',
27 | 'replacement' => "\$1\$2\n\$1 '%s',",
28 | ],
29 | self::TYPE_MODULE => [
30 | 'pattern' => "/('modules'\s*\=\>\s*(?:array\s*\(|\[).*?)\n(\s+)(\)|\])/s",
31 | 'replacement' => "\$1\n\$2 '%s',\n\$2\$3",
32 | ],
33 | self::TYPE_DEPENDENCY => [
34 | 'pattern' => '/^(\s+)(\'modules\'\s*\=\>\s*(?:array\s*\(|\[)[^)\]]*\'%s\')/m',
35 | 'replacement' => "\$1\$2,\n\$1 '%s'",
36 | ],
37 | self::TYPE_BEFORE_APPLICATION => [
38 | 'pattern' => '/^(\s+)(\'modules\'\s*\=\>\s*(?:array\s*\(|\[)[^)\]]*)(\'%s\')/m',
39 | 'replacement' => "\$1\$2'%s',\n$1 \$3",
40 | ],
41 | ];
42 |
43 | /**
44 | * Pattern to use to determine if the code item is registered.
45 | *
46 | * @var string
47 | */
48 | protected $isRegisteredPattern = '/\'modules\'\s*\=\>\s*(?:array\(|\[)[^)\]]*\'%s\'/s';
49 |
50 | /**
51 | * Patterns and replacements to use when removing a code item.
52 | *
53 | * @var string[]
54 | */
55 | protected $removalPatterns = [
56 | 'pattern' => '/^\s+\'%s\',\s*$/m',
57 | 'replacement' => '',
58 | ];
59 | }
60 |
--------------------------------------------------------------------------------
/src/Injector/ConditionalDiscoveryTrait.php:
--------------------------------------------------------------------------------
1 | validConfigAggregatorConfig()) {
23 | return false;
24 | }
25 |
26 | return parent::inject('\\' . $package, $type);
27 | }
28 |
29 | /**
30 | * {@inheritDoc}
31 | *
32 | * Prepends the package with a `\\` in order to ensure it is fully
33 | * qualified, preventing issues in config files that are namespaced.
34 | */
35 | public function remove($package)
36 | {
37 | if (! $this->validConfigAggregatorConfig()) {
38 | return false;
39 | }
40 |
41 | return parent::remove('\\' . $package);
42 | }
43 |
44 | /**
45 | * Does the config file hold valid ConfigAggregator configuration?
46 | *
47 | * @return bool
48 | */
49 | private function validConfigAggregatorConfig()
50 | {
51 | $discoveryClass = $this->discoveryClass;
52 | $discovery = new $discoveryClass($this->getProjectRoot());
53 | return $discovery->locate();
54 | }
55 |
56 | /**
57 | * Calculate the project root from the config file
58 | *
59 | * @return string
60 | */
61 | private function getProjectRoot()
62 | {
63 | if (static::DEFAULT_CONFIG_FILE === $this->configFile) {
64 | return '';
65 | }
66 | return str_replace('/' . static::DEFAULT_CONFIG_FILE, '', $this->configFile);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Injector/ConfigAggregatorInjector.php:
--------------------------------------------------------------------------------
1 | [
52 | 'pattern' => '',
53 | 'replacement' => "\$1\n\$2%s::class,\n\$2",
54 | ],
55 | ];
56 |
57 | /**
58 | * Pattern to use to determine if the code item is registered.
59 | *
60 | * Set in constructor due to PCRE quoting issues.
61 | *
62 | * @var string
63 | */
64 | protected $isRegisteredPattern = '';
65 |
66 | /**
67 | * Patterns and replacements to use when removing a code item.
68 | *
69 | * @var string[]
70 | */
71 | protected $removalPatterns = [
72 | 'pattern' => '/^\s+%s::class,\s*$/m',
73 | 'replacement' => '',
74 | ];
75 |
76 | /**
77 | * {@inheritDoc}
78 | *
79 | * Sets $isRegisteredPattern and pattern for $injectionPatterns to ensure
80 | * proper PCRE quoting.
81 | */
82 | public function __construct($projectRoot = '')
83 | {
84 | $ns = preg_quote('\\');
85 | $this->isRegisteredPattern = '/new (?:'
86 | . $ns
87 | . '?'
88 | . preg_quote('Zend\ConfigAggregator\\')
89 | . ')?ConfigAggregator\(\s*(?:array\(|\[).*\s+'
90 | . $ns
91 | . '?%s::class/s';
92 |
93 | $this->injectionPatterns[self::TYPE_CONFIG_PROVIDER]['pattern'] = sprintf(
94 | "/(new (?:%s?%s)?ConfigAggregator\(\s*(?:array\(|\[)\s*)(?:\r|\n|\r\n)(\s*)/",
95 | preg_quote('\\'),
96 | preg_quote('Zend\ConfigAggregator\\')
97 | );
98 |
99 | parent::__construct($projectRoot);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Injector/ConfigInjectorChain.php:
--------------------------------------------------------------------------------
1 | chain = Collection::create($injectors)
51 | // Keep only those injectors that discovery exists in discoveryChain
52 | ->filter(function ($injector, $file) use ($discoveryChain) {
53 | return $discoveryChain->discoveryExists($file);
54 | })
55 | // Create an injector for the config file
56 | ->map(function ($injector) use ($projectRoot) {
57 | return new $injector($projectRoot);
58 | })
59 | // Keep only those injectors that match types available for the package
60 | ->filter(function ($injector) use ($availableTypes) {
61 | return $availableTypes->reduce(function ($flag, $type) use ($injector) {
62 | return $flag || $injector->registersType($type);
63 | }, false);
64 | });
65 | }
66 |
67 | /**
68 | * {@inheritDoc}
69 | */
70 | public function registersType($type)
71 | {
72 | return in_array($type, $this->getTypesAllowed(), true);
73 | }
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | public function getTypesAllowed()
79 | {
80 | if ($this->allowedTypes) {
81 | return $this->allowedTypes;
82 | }
83 | $allowedTypes = [];
84 | foreach ($this->chain->getIterator() as $injector) {
85 | $allowedTypes = $allowedTypes + $injector->getTypesAllowed();
86 | }
87 | $this->allowedTypes = $allowedTypes;
88 | return $allowedTypes;
89 | }
90 |
91 | /**
92 | * {@inheritDoc}
93 | */
94 | public function isRegistered($package)
95 | {
96 | $isRegisteredCount = $this->chain
97 | ->filter(function ($injector) use ($package) {
98 | return $injector->isRegistered($package);
99 | })
100 | ->count();
101 | return $this->chain->count() === $isRegisteredCount;
102 | }
103 |
104 | /**
105 | * {@inheritDoc}
106 | */
107 | public function inject($package, $type)
108 | {
109 | $injected = false;
110 |
111 | $this->chain
112 | ->each(function ($injector) use ($package, $type, &$injected) {
113 | $injected = $injector->inject($package, $type) || $injected;
114 | });
115 |
116 | return $injected;
117 | }
118 |
119 | /**
120 | * {@inheritDoc}
121 | */
122 | public function remove($package)
123 | {
124 | $removed = false;
125 |
126 | $this->chain
127 | ->each(function ($injector) use ($package, &$removed) {
128 | $removed = $injector->remove($package) || $removed;
129 | });
130 |
131 | return $removed;
132 | }
133 |
134 | /**
135 | *
136 | * @return Collection
137 | */
138 | public function getCollection()
139 | {
140 | return $this->chain;
141 | }
142 |
143 | /**
144 | * {@inheritDoc}
145 | */
146 | public function setApplicationModules(array $modules)
147 | {
148 | return $this;
149 | }
150 |
151 | /**
152 | * {@inheritDoc}
153 | */
154 | public function setModuleDependencies(array $modules)
155 | {
156 | return $this;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Injector/DevelopmentConfigInjector.php:
--------------------------------------------------------------------------------
1 | [
52 | 'pattern' => '',
53 | 'replacement' => "\$1\n %s::class,",
54 | ],
55 | ];
56 |
57 | /**
58 | * Pattern to use to determine if the code item is registered.
59 | *
60 | * Set in constructor due to PCRE quoting issues.
61 | *
62 | * @var string
63 | */
64 | protected $isRegisteredPattern = '';
65 |
66 | /**
67 | * Patterns and replacements to use when removing a code item.
68 | *
69 | * @var string[]
70 | */
71 | protected $removalPatterns = [
72 | 'pattern' => '/^\s+%s::class,\s*$/m',
73 | 'replacement' => '',
74 | ];
75 |
76 | /**
77 | * {@inheritDoc}
78 | *
79 | * Sets $isRegisteredPattern and pattern for $injectionPatterns to ensure
80 | * proper PCRE quoting.
81 | */
82 | public function __construct($projectRoot = '')
83 | {
84 | $this->isRegisteredPattern = '/new (?:'
85 | . preg_quote('\\')
86 | . '?'
87 | . preg_quote('Zend\Expressive\ConfigManager\\')
88 | . ')?ConfigManager\(\s*(?:array\(|\[).*\s+%s::class/s';
89 |
90 | $this->injectionPatterns[self::TYPE_CONFIG_PROVIDER]['pattern'] = sprintf(
91 | '/(new (?:%s?%s)?ConfigManager\(\s*(?:array\(|\[)\s*)$/m',
92 | preg_quote('\\'),
93 | preg_quote('Zend\Expressive\ConfigManager\\')
94 | );
95 |
96 | parent::__construct($projectRoot);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Injector/InjectorInterface.php:
--------------------------------------------------------------------------------
1 | [
26 | 'pattern' => '/^(return\s+(?:array\s*\(|\[))\s*$/m',
27 | 'replacement' => "\$1\n '%s',",
28 | ],
29 | self::TYPE_MODULE => [
30 | 'pattern' => "/(return\s+(?:array\s*\(|\[).*?)\n(\s*)(\)|\])/s",
31 | 'replacement' => "\$1\n\$2 '%s',\n\$2\$3",
32 | ],
33 | self::TYPE_DEPENDENCY => [
34 | 'pattern' => '/^(return\s+(?:array\s*\(|\[)[^)\]]*\'%s\')/m',
35 | 'replacement' => "\$1,\n '%s'",
36 | ],
37 | self::TYPE_BEFORE_APPLICATION => [
38 | 'pattern' => '/^(return\s+(?:array\s*\(|\[)[^)\]]*)(\'%s\')/m',
39 | 'replacement' => "\$1'%s',\n \$2",
40 | ],
41 | ];
42 |
43 | /**
44 | * Pattern to use to determine if the code item is registered.
45 | *
46 | * @var string
47 | */
48 | protected $isRegisteredPattern = '/return\s+(?:array\(|\[)[^)\]]*\'%s\'/s';
49 |
50 | /**
51 | * Patterns and replacements to use when removing a code item.
52 | *
53 | * @var string[]
54 | */
55 | protected $removalPatterns = [
56 | 'pattern' => '/^\s+\'%s\',\s*$/m',
57 | 'replacement' => '',
58 | ];
59 | }
60 |
--------------------------------------------------------------------------------
/src/Injector/NoopInjector.php:
--------------------------------------------------------------------------------
1 |