88[ ![ Slack] ( https://img.shields.io/badge/slack-%23humbug-red.svg?style=flat-square )] ( https://symfony.com/slack-invite )
99[ ![ License] ( https://img.shields.io/badge/license-MIT-red.svg?style=flat-square )] ( LICENSE )
1010
11- PHP-Scoper is a tool for adding a prefix to all PHP namespaces in a given file
12- or directory.
13-
11+ PHP-Scoper is a tool which essentially moves any body of code, including all
12+ dependencies such as vendor directories, to a new and distinct namespace.
1413
1514## Goal
1615
17- PHP-Scoper's goal is to make sure that all code in a directory lies in a
18- distinct PHP namespace. This is necessary when building PHARs that
16+ PHP-Scoper's goal is to make sure that all code for a project lies in a
17+ distinct PHP namespace. This is necessary, for example, when building PHARs that:
18+
19+ * Bundle their own vendor dependencies; and
20+ * Load/execute code from arbitrary PHP projects with similar dependencies
1921
20- * Bundle their own vendor dependencies
21- * Load code of arbitrary PHP projects
22+ When a package (of possibly different versions) exists, and is found in both a PHAR
23+ and the executed code, the one from the PHAR will be used. This means these
24+ PHARs run the risk of raising conflicts between their bundled vendors and the
25+ vendors of the project they are interacting with, leading to issues that are
26+ potentially very difficult to debug due to dissimilar or unsupported package versions.
2227
23- These PHARs run the risk of raising conflicts between their bundled vendors and
24- the vendors of the loaded project, if the vendors are required in incompatible
25- versions.
28+ ## Table of Contents
2629
30+ - [ Installation] ( #installation )
31+ - [ Usage] ( #usage )
32+ - [ Patchers] ( #patchers )
33+ - [ Global Namespace Whitelisting] ( #global-namespace-whitelisting )
34+ - [ Contributing] ( #contributing )
35+ - [ Building A Scoped PHAR] ( #building-a-scoped-phar )
36+ - [ Credits] ( #credits )
2737
2838## Installation
2939
40+ ### PHAR (preferred)
41+
42+ The preferred method of installation is to use the PHP-Scoper PHAR, which can
43+ be downloaded from the most recent [ Github Release] ( releases ) . Subsequent updates
44+ can be downloaded by running:
45+
46+ ``` bash
47+ php-scoper.phar self-update
48+ ```
49+
50+ As the PHAR is signed, you should also download the matching
51+ ` php-scoper.phar.pubkey ` to the same location. If you rename ` php-scoper.phar `
52+ to ` php-scoper ` , you should also rename ` php-scoper.phar.pubkey ` to
53+ ` php-scoper.pubkey ` .
54+
55+ ### Composer
56+
3057You can install PHP-Scoper with Composer:
3158
3259``` bash
33- composer global require humbug/php-scoper:dev-master
60+ composer global require humbug/php-scoper
3461```
3562
3663If you cannot install it because of a dependency conflict or you prefer to
@@ -39,41 +66,211 @@ install it for your project, we recommend you to take a look at
3966
4067``` bash
4168composer require --dev bamarni/composer-bin-plugin
42- composer bin php-scoper require --dev humbug/php-scoper:dev-master
69+ composer bin php-scoper require --dev humbug/php-scoper
4370```
4471
45- A PHAR should be availaible soon as well.
46-
47-
4872## Usage
4973
5074``` bash
5175php-scoper add-prefix
5276```
5377
54- This will prefix all the files found in the current working directory.
55- The prefixed files will be accessible in a ` build ` folder. You can
78+ This will prefix all relevant namespaces in code found in the current working
79+ directory. The prefixed files will be accessible in a ` build ` folder. You can
5680then use the prefixed code to build your PHAR.
5781
58- ** Warning** : After prefexing the files, if you are relying on Composer
82+ ** Warning** : After prefixing the files, if you are relying on Composer
5983for the autoloading, dumping the autoloader again is required.
6084
6185For a more concrete example, you can take a look at PHP-Scoper's build
62- step in [ Makefile] ( Makefile ) .
86+ step in [ Makefile] ( Makefile ) , especially if you are using Composer as
87+ there are steps both before and after running PHP-Scoper to consider.
88+
89+ Refer to TBD for an in-depth look at scoping and building a PHAR taken from
90+ PHP-Scoper's makefile.
91+
92+ ## Patchers
93+
94+ When scoping PHP files, there will be scenarios where some of the code being
95+ scoped indirectly references the original namespace. These will include, for
96+ example, strings or string manipulations. PHP-Scoper has limited support for
97+ prefixing such strings, so you may need to define ` patchers ` , one or more
98+ callables in a ` scoper.inc.php ` configuration file which can be used to replace
99+ some of the code being scoped.
100+
101+ Here's a simple example:
102+
103+ * Class names in strings.
104+
105+ You can imagine instantiating a class from a variable which is based on a
106+ known namespace, but also on a variable classname which is selected at
107+ runtime. Perhaps code similar to:
108+
109+ ``` php
110+ $type = 'Foo'; // determined at runtime
111+ $class = 'Humbug\\Format\\Type\\' . $type;
112+ ```
113+
114+ If we scoped the ` Humbug ` namespace to ` PhpScoperABC\Humbug ` , then the above
115+ snippet would fail as PHP-Scoper cannot interpret the above as being a namespaced
116+ class. To complete the scoping successfully, a) the problem must
117+ be located and b) the offending line replaced.
118+
119+ The patched code which would resolve this issue might be:
120+
121+ ``` php
122+ $type = 'Foo'; // determined at runtime
123+ $scopedPrefix = array_shift(explode('\\', __NAMESPACE__));
124+ $class = $scopedPrefix . '\\Humbug\\Format\\Type\\' . $type;
125+ ```
126+
127+ This and similar issues * may* arise after scoping, and can be debugged by
128+ running the scoped code and checking for issues. For this purpose, having a
129+ couple of end to end tests to validate post-scoped code or PHARs is recommended.
130+
131+ Applying such a change can be achieved by defining a suitable patcher in
132+ ` scoper.inc.php ` :
133+
134+ ``` php
135+ <?php declare(strict_types=1)
136+
137+ // scoper.inc.php
138+
139+ return [
140+ 'patchers' => [
141+ function (string $filePath, string $prefix, string $content): string {
142+ //
143+ // PHP-Parser patch conditions for file targets
144+ //
145+ if ($filePath === '/path/to/offending/file') {
146+ return preg_replace(
147+ "%\$class = 'Humbug\\\\Format\\\\Type\\\\' . \$type;%",
148+ '$class = \'' . $prefix . '\\\\Humbug\\\\Format\\\\Type\\\\\' . $type;',
149+ $content
150+ );
151+ }
152+ return $content;
153+ },
154+ ],
155+ ];
156+ ```
157+
158+ ## Global Namespace Whitelisting
159+
160+ By default, PHP-Scoper only scopes (or prefixes) code where the namespace is
161+ non-global. In other words, non-namespaced code is not scoped. This leaves the
162+ majority of classes, functions and constants in PHP, and most extensions,
163+ untouched.
164+
165+ This is not necessarily a desireable outcome for vendor dependencies which are
166+ also not namespaced. To ensure they are isolated, you can configure PHP-Scoper to
167+ allow their prefixing from ` scoper.inc.php ` using basic strings or callables:
168+
169+ ``` php
170+ <?php declare(strict_types=1)
63171
172+ // scoper.inc.php
173+
174+ return [
175+ 'global_namespace_whitelist' => [
176+ 'AppKernel',
177+ function ($className) {
178+ return 'PHPUnit' === substr($className, 0, 6);
179+ },
180+ ],
181+ 'patchers' => [
182+ // patchers if relevant
183+ ],
184+ ];
185+ ```
186+
187+ In this example, we're ensuring that the ` AppKernal ` class, and any
188+ non-namespaced PHPUnit packages are prefixed.
64189
65190## Contributing
66191
67192[ Contribution Guide] ( CONTRIBUTING.md )
68193
194+ ## Building A Scoped PHAR
195+
196+ This is a brief run through of the basic steps encoded in PHP-Scoper's own
197+ [ Makefile] ( Makefile ) and elsewhere to build a PHAR from scoped code.
198+
199+ ### Step 1: Configure build location and prep vendors
200+
201+ If, for example, you are using [ Box] ( box ) to build your PHAR, you
202+ should set the ` base-path ` configuration option in your ` box.json ` file
203+ to point at the directory which will host scoped code. PHP-Scoper,
204+ by default, creates a ` build ` directory relative to the current working
205+ directory.
206+
207+ ``` js
208+ " base-path" : " build"
209+ ```
210+
211+ Assuming you need no dev dependencies, run:
212+
213+ ``` bash
214+ composer install --no-dev --prefer-dist
215+ ```
216+
217+ ### Step 2: Run PHP-Scoper
218+
219+ PHP-Scoper copies code to a new location during prefixing, leaving your original
220+ code untouched. The default location is ` ./build ` . You can change the default
221+ location using the ` --output-dir ` option. By default, it also generates a random
222+ prefix string. You can set a specific prefix string using the ` --prefix ` option.
223+ If automating builds, you can set the ` --force ` option to overwrite any code
224+ existing in the output directory without being asked to confirm.
225+
226+ Onto the basic command assuming default options from your project's root
227+ directory:
228+
229+ ``` bash
230+ bin/php-scoper add-prefix
231+ ```
232+
233+ As there are no path arguments, the current working directory will be scoped to
234+ ` ./build ` in its entirety. Of course, actual prefixing is limited to PHP files,
235+ or PHP scripts. Other files are copied unchanged, though we also need to scope
236+ certain Composer related files.
237+
238+ Speaking of scoping Composer related files... The next step is to dump the
239+ Composer autoloader if we depend on it, so everything works as expected:
240+
241+ ``` bash
242+ composer dump-autoload -d build --classmap-authoritative
243+ ```
244+
245+ ### Step 3: Build, test, and cleanup
246+
247+ If using [ Box] ( box ) , you can now move onto actually building the PHAR:
248+
249+ ``` bash
250+ php -d phar.readonly=0 box build -vvv
251+ ```
252+
253+ At this point, it's best to have some simple end-to-end tests automated to put
254+ the PHAR through its paces and locate any problems (see Patchers and Whitelists
255+ from earlier in this README). Assuming it passes testing, the PHAR is ready.
256+
257+ Cleanup is simply to optionally delete ` ./build ` contents, and remember to
258+ re-install dev dependencies removed during Step 1:
259+
260+ ``` bash
261+ composer install
262+ ```
69263
70264## Credits
71265
72- Project originally created by: [ Bernhard Schussek] ([ @webmozart ] ) which has then been moved under the
266+ Project originally created by: [ Bernhard Schussek] ([ @webmozart ] ) which has
267+ now been moved under the
73268[ Humbug umbrella] [ humbug ] .
74269
75270
76271[ Bernhard Schussek ] : https://webmozart.io/
77272[ @webmozart ] : https://twitter.com/webmozart
78273[ humbug ] : https://github.com/humbug
79- [ bamarni/composer-bin-plugin ] : https://github.com/bamarni/composer-bin-plugin
274+ [ bamarni/composer-bin-plugin ] : https://github.com/bamarni/composer-bin-plugin
275+ [ box ] : https://github.com/box-project/box2
276+ [ releases ] : https://github.com/humbug/php-scoper/releases
0 commit comments