├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── autoload.php
├── composer.json
├── src
├── AutoloaderBootstrap.php
├── AutoloaderBootstrapInterface.php
├── ClassLoaderException.php
├── Discovery
│ ├── PathFinderBase.php
│ ├── PathFinderContrib.php
│ ├── PathFinderCore.php
│ ├── PathFinderInterface.php
│ └── PathFinderNull.php
├── Loader.php
├── LoaderInterface.php
├── TokenResolver.php
├── TokenResolverFactory.php
├── TokenResolverFactoryInterface.php
└── TokenResolverInterface.php
└── tests
├── .coveralls.yml
├── .gitignore
├── composer.json
├── data
├── acme.inc
└── docroot
│ ├── file.inc
│ ├── includes
│ └── common.inc
│ ├── index.php
│ ├── misc
│ └── drupal.js
│ ├── modules
│ └── field
│ │ └── field.module
│ └── sites
│ └── all
│ └── modules
│ └── testmodule
│ ├── composer.json
│ └── testmodule.info
├── install.sh
├── phpunit.xml
├── run-coverage.sh
├── run-tests.sh
└── src
├── AutoloaderBootstrapTest.php
├── Discovery
├── PathFinderBaseTest.php
├── PathFinderContribTest.php
├── PathFinderCoreTest.php
└── PathFinderNullTest.php
├── LoaderTest.php
├── TokenResolverFactoryTest.php
└── TokenResolverTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 |
8 | matrix:
9 | fast_finish: true
10 |
11 | env:
12 | global:
13 | # add composer's global bin directory to the path
14 | # see: https://github.com/drush-ops/drush#install---composer
15 | - PATH="$PATH:$HOME/.composer/vendor/bin"
16 |
17 | script:
18 | - cd ./tests
19 | - ./run-tests.sh
20 | - ./run-coverage.sh
21 | - cd ..
22 |
23 | after_script:
24 | - cd ./tests
25 | - php vendor/bin/coveralls
26 |
27 |
28 | notifications:
29 | webhooks:
30 | urls:
31 | - https://webhooks.gitter.im/e/c2147c4ed13ddd031b30
32 | - https://webhooks.gitter.im/e/45a8fac3fb8fd2ae907d
33 | on_success: change
34 | on_failure: always
35 | on_start: false
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://coveralls.io/github/mateu-aguilo-bosch/drupal-unit-autoload?branch=master) [](https://travis-ci.org/e0ipso/drupal-unit-autoload) [](https://scrutinizer-ci.com/g/e0ipso/drupal-unit-autoload/?branch=master)
2 |
3 | [](https://gitter.im/e0ipso/drupal-unit-autoload?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
4 |
5 | # Drupal Unit Autoload
6 |
7 | Have you ever wanted to add **PHPUnit** tests to your Drupal 7 module? Well, you should. This tool aims to help you to deal
8 | with autoloading the classes that you have in your Drupal installation.
9 |
10 | ## The Problem
11 | The main problem arises when the class -or code- that you are testing depends on classes declared in other modules, or
12 | Drupal core itself.
13 |
14 | Imagine that you are testing your `Car` class. That class depends on `\DrupalCacheInterface` (you are using a mock cache
15 | provider that **has** to implement that interface), and also depends on several classes from the service container
16 | module. After all you are injecting services in your `Car` class to be able to mock them afterwards, and that may
17 | require to have the `Drupal\service_container\` available to you during tests.
18 |
19 | Since you are doing unit testing, you may not want to bootstrap Drupal to have the database available in order to be
20 | able to check in the registry to find all those classes.
21 |
22 | At this point you can think _I will just use Composer's autoloader and define where to find those classes and
23 | namespaces_. This is when you realize that Drupal allows you to install contrib modules in **many** locations. That
24 | makes it impossible to ship your module with the relative paths that you need.
25 |
26 | Imagine this possibility:
27 | - Our custom module (the one that will have unit testing) is installed in `sites/example.org/modules/contrib/racing_modules/car`.
28 | - The modules that the _car_ module depends on are installed in:
29 | - `sites/all/modules/essentials/service_container`.
30 | - `sites/default/modules/contrib/dependency`.
31 |
32 | It seems that if you wanted to provide the path to `includes/cache.inc` to make `\DrupalCacheInterface` available, then
33 | you would need to add a path like: `../../../../../../includes/cache.inc`. But what if someone decides to install your
34 | `car` module in `sites/all/modules/car`? That path you provided in the module will not work in that situation. The
35 | correct one would be `../../../includes/cache.inc`. Basically every site installation may need a different path.
36 |
37 | The problem that this project aims to solve is to give you a way to provide a single path in your code that will work in
38 | all those scenarios.
39 |
40 | ## The Solution
41 | Meet the Drupal Unit Autoload. To include it, just add the following to your PHPUnit test class (change the path
42 | depending on the location of your test classes):
43 |
44 | ```php
45 | require_once __DIR__ . '/../../vendor/e0ipso/drupal-unit-autoload/autoload.php';
46 | ```
47 |
48 | That will load Composer's autoloader + the Drupal capabilities.
49 |
50 | The only thing that you need to do is add a new `composer.json` key with tokens in the path.
51 |
52 | Inside the folder where you have your unit tests you will need to have a `composer.json` file that has:
53 |
54 | ```js
55 | {
56 | "require-dev": {
57 | "phpunit/phpunit": "4.7.*",
58 | "mockery/mockery": "0.9.*",
59 | "e0ipso/drupal-unit-autoload": "1.0.*"
60 | },
61 | "autoload": {
62 | "psr-0": {
63 | … This is usual Composer business …
64 | },
65 | "psr-4": {
66 | … This is usual Composer business …
67 | "Drupal\\service_container\\": "DRUPAL_CONTRIB/src",
68 | "Drupal\\Core\\": [
69 | "DRUPAL_CONTRIB/lib/Core",
70 | "DRUPAL_CONTRIB/src/DrupalCore"
71 | ]
72 | },
73 | "class-location": {
74 | "\\DrupalCacheInterface": "DRUPAL_ROOT/includes/cache.inc",
75 | "\\ServiceContainer": "DRUPAL_CONTRIB/lib/ServiceContainer.php",
76 | "\\Drupal": "DRUPAL_CONTRIB/lib/Drupal.php"
77 | }
78 | }
79 | }
80 | ```
81 |
82 | Running `composer install` on that folder will download PHPUnit, Mockery -and all of the tools that you use for your
83 | tests-. Additionally it will download this project, that is what `"e0ipso/drupal-unit-autoload": "0.1.*"` is
84 | for.
85 |
86 | At this point you only need is add the paths with `DRUPAL_ROOT` or `DRUPAL_CONTRIB` in your composer file.
87 |
88 | You have two options:
89 | - Provide class names and the files where they are found using the new key inside of `autoload` called `class-location`.
90 | - Provide psr-4 and psr-0 namespace prefixes and the path where they are mapped. This is very simmilar to what
91 | composer does, but with the magic that finds where the Drupal root is and where the contrib modules are installed.
92 |
93 | In the paths that you provide, you will be able to include two tokens: `DRUPAL_ROOT` and `DRUPAL_CONTRIB`.
94 | Those tokens will be expanded to the real paths that they represent. This way, providing `DRUPAL_CONTRIB` can end up expanding in:
95 | - `/var/www/docroot/sites/all/modules/ctools` in one Drupal installation.
96 | - `/User/Sites/drupal-site/sites/default/modules/contrib/ctools` in another installation.
97 |
98 | The important thing to note is that your code ships with the same _tokenized_ path for everyone, without caring about
99 | where the dependencies are installed.
100 |
--------------------------------------------------------------------------------
/autoload.php:
--------------------------------------------------------------------------------
1 | register();
10 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e0ipso/drupal-unit-autoload",
3 | "description": "Allows you to load classes in non standard unknown file locations.",
4 | "minimum-stability": "stable",
5 | "license": "GPL-2.0",
6 | "authors": [
7 | {
8 | "name": "Mateu Aguiló Bosch",
9 | "email": "mateu.aguilo.bosch@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=5.4.0"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "Drupal\\Composer\\ClassLoader\\": "src"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/AutoloaderBootstrap.php:
--------------------------------------------------------------------------------
1 | classLoader = $classLoader;
64 | $this->seed = $seed;
65 | $this->loader = $loader ?: new Loader($seed);
66 | $this->tokenFactory = $token_factory ?: new TokenResolverFactory();
67 | }
68 |
69 | /**
70 | * {@inheritdoc}
71 | */
72 | public function register() {
73 | if ($this->checkLoadedAutoloader()) {
74 | return;
75 | }
76 | // Parse the composer.json.
77 | $composer_config = $this->getConfig();
78 | $this->registerDrupalPaths($composer_config);
79 | $this->registerPsr($composer_config);
80 | }
81 |
82 | /**
83 | * Registers the autoloader.
84 | */
85 | protected function load() {
86 | spl_autoload_register(array($this->loader, static::AUTOLOAD_METHOD));
87 | }
88 |
89 | /**
90 | * Unregisters the autoloader.
91 | */
92 | protected function unload() {
93 | spl_autoload_unregister(array($this->loader, static::AUTOLOAD_METHOD));
94 | }
95 |
96 | /**
97 | * Register the path based autoloader.
98 | *
99 | * @param object $composer_config
100 | * The Composer configuration.
101 | */
102 | protected function registerDrupalPaths($composer_config) {
103 | if (empty($composer_config['class-location'])) {
104 | return;
105 | }
106 | $this->loader->setClassMap((array) $composer_config['class-location']);
107 | $this->load();
108 | }
109 |
110 | /**
111 | * Use Composer's autoloader to register the PRS-0 and PSR-4 paths.
112 | *
113 | * @param array $composer_config
114 | * The Composer configuration.
115 | */
116 | protected function registerPsr(array $composer_config) {
117 | $psr0 = $psr4 = array();
118 | if (!empty($composer_config['psr-0'])) {
119 | $psr0 = (array) $composer_config['psr-0'];
120 | }
121 | if (!empty($composer_config['psr-4'])) {
122 | $psr4 = (array) $composer_config['psr-4'];
123 | }
124 | if (empty($psr4) && empty($psr0)) {
125 | return;
126 | }
127 | $this->loader->setPsrClassMap(array(
128 | 'psr-0' => $psr0,
129 | 'psr-4' => $psr4,
130 | ));
131 | $this->loader->registerPsr($this->classLoader);
132 | }
133 |
134 | /**
135 | * {@inheritdoc}
136 | */
137 | public function checkLoadedAutoloader() {
138 | $functions = spl_autoload_functions();
139 | return in_array(array($this->loader, static::AUTOLOAD_METHOD), $functions);
140 | }
141 |
142 | /**
143 | * {@inheritdoc}
144 | */
145 | public function getConfig() {
146 | // Initialize empty configuration.
147 | $config = [
148 | 'psr-0' => [],
149 | 'psr-4' => [],
150 | 'class-location' => [],
151 | ];
152 |
153 | // Find the tokenized paths.
154 | $psrs = array(
155 | 'psr-0' => $this->classLoader->getPrefixes(),
156 | 'psr-4' => $this->classLoader->getPrefixesPsr4(),
157 | );
158 | // Get all the PSR-0 and PSR-0 and detect the ones that have Drupal tokens.
159 | foreach ($psrs as $psr_type => $namespaces) {
160 | $namespaces = $namespaces ?: [];
161 | foreach ($namespaces as $prefix => $paths) {
162 | $token_paths = array();
163 | if (!is_array($paths)) {
164 | $paths = array($paths);
165 | }
166 | foreach ($paths as $path) {
167 | $token_resolver = $this->tokenFactory->factory($path);
168 | if (!$token_resolver->hasToken()) {
169 | continue;
170 | }
171 | $path = $token_resolver->trimPath();
172 | $token_paths[] = $path;
173 | }
174 | // If there were paths, add them to the config.
175 | if (!empty($token_paths)) {
176 | $config[$psr_type][$prefix] = $token_paths;
177 | }
178 | }
179 | }
180 |
181 | // Get the drupal path configuration.
182 | $composer_config = json_decode(file_get_contents(static::COMPOSER_CONFIGURATION_NAME));
183 | $config['class-location'] = array();
184 | if (isset($composer_config->autoload->{'class-location'})) {
185 | $config['class-location'] = array_merge($config['class-location'], (array) $composer_config->autoload->{'class-location'});
186 | }
187 | if (isset($composer_config->{'autoload-dev'}->{'class-location'})) {
188 | $config['class-location'] = array_merge($config['class-location'], (array) $composer_config->{'autoload-dev'}->{'class-location'});
189 | }
190 |
191 | return $config;
192 | }
193 |
194 | /**
195 | * {@inheritdoc}
196 | */
197 | public function getClassLoader() {
198 | return $this->classLoader;
199 | }
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/src/AutoloaderBootstrapInterface.php:
--------------------------------------------------------------------------------
1 | path = $options[0];
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function requireFile($seed) {
35 | // Get the real path to the file.
36 | $real_path = $this->find($seed);
37 | require_once $real_path;
38 | }
39 |
40 | /**
41 | * Cleans a directory path by removing /. from the end.
42 | *
43 | * @param string $dir_path
44 | * The path name to clean.
45 | *
46 | * @return string
47 | * The clean path name.
48 | */
49 | protected function cleanDirPath($dir_path) {
50 | // Remove annoying /. at the end. This is needed because the
51 | // DirectoryIterator adds that to the end of the dir name.
52 | $dir_path = rtrim($dir_path, '.');
53 | $dir_path = rtrim($dir_path, DIRECTORY_SEPARATOR);
54 | return $dir_path;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Discovery/PathFinderContrib.php:
--------------------------------------------------------------------------------
1 | path = $options[0];
40 | $this->moduleName = $options[1];
41 | $this->coreFinder = $core_finder ?: new PathFinderCore(array(''));
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function find($seed) {
48 | // To find contrib, we need to know where core is first. Pass the empty
49 | // string to as the path so we get the path for core itself -and not a path
50 | // relative to the core install-.
51 | $core_path = $this->coreFinder->find($seed);
52 |
53 | // Create the RecursiveDirectoryIterator on the core directory.
54 | $core_directory = new \RecursiveDirectoryIterator($core_path . '/sites', \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS);
55 | // Create an iterator that will go recursively through all the files and
56 | // directories -because of SELF_FIRST-.
57 | $files_iterator = new \RecursiveIteratorIterator($core_directory, \RecursiveIteratorIterator::SELF_FIRST);
58 | // Iterate over all of the directories under the sites directory.
59 | foreach ($files_iterator as $path_name => $dir) {
60 | /** @var $dir \SplFileInfo */
61 | if (!$dir->isDir()) {
62 | // Do not scan all files, just directories.
63 | continue;
64 | }
65 | // Check if the current directory corresponds to the contrib we are
66 | // looking for.
67 | if ($this->isWantedContrib($dir)) {
68 | return $this->cleanDirPath($dir->getPathName()) . $this->path;
69 | }
70 | }
71 | throw new ClassLoaderException(sprintf('Drupal module "%s" could not be found in the Drupal tree that contains: %s.', $this->moduleName, $seed));
72 | }
73 |
74 | /**
75 | * Checks if the passed directory is the contrib module we are looking for.
76 | *
77 | * @param \SplFileInfo $dir
78 | * The info object about the directory.
79 | * @return bool
80 | * TRUE if the contrib is detected. FALSE otherwise.
81 | */
82 | protected function isWantedContrib(\SplFileInfo $dir) {
83 | $info_file = $this->cleanDirPath($dir->getPathName()) . DIRECTORY_SEPARATOR . $this->moduleName . '.info';
84 | return file_exists($info_file);
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/Discovery/PathFinderCore.php:
--------------------------------------------------------------------------------
1 | isDrupalRoot($directory)) {
25 | return $directory . $this->path;
26 | }
27 | }
28 | while ($directory = $this->getParentDirectory($directory));
29 | // @codeCoverageIgnoreStart
30 | // If we have not returned, that means that the Drupal core directory could
31 | // not be found.
32 | throw new ClassLoaderException(sprintf('Drupal core directory could not be found as a parent of: %s.', $seed));
33 | // @codeCoverageIgnoreEnd
34 | }
35 |
36 | /**
37 | * Checks if the passed directory is the Drupal root.
38 | *
39 | * @param string $directory
40 | * The directory path.
41 | *
42 | * @return bool
43 | * TRUE if the passed directory is the Drupal root.
44 | */
45 | protected function isDrupalRoot($directory) {
46 | if (!empty($directory) && is_dir($directory) && file_exists($directory . DIRECTORY_SEPARATOR . '/index.php')) {
47 | // Drupal 7 root.
48 | // We check for the presence of 'modules/field/field.module' to differentiate this from a D6 site
49 | return (file_exists($directory . DIRECTORY_SEPARATOR . 'includes/common.inc')
50 | && file_exists($directory . DIRECTORY_SEPARATOR . 'misc/drupal.js')
51 | && file_exists($directory . DIRECTORY_SEPARATOR . 'modules/field/field.module'));
52 | }
53 | return FALSE;
54 | }
55 |
56 | /**
57 | * Gets the parent directory iterator.
58 | *
59 | * @param string $directory
60 | * The current directory path.
61 | *
62 | * @throws ClassLoaderException
63 | * If no parent directory could be found.
64 | *
65 | * @return string
66 | * The parent directory.
67 | */
68 | protected function getParentDirectory($directory) {
69 | // Get the parent directory.
70 | if ($directory === realpath('/')) {
71 | throw new ClassLoaderException(sprintf('Could not find the parent directory of "%s".', $directory));
72 | }
73 | return dirname($directory);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/Discovery/PathFinderInterface.php:
--------------------------------------------------------------------------------
1 | path;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/Loader.php:
--------------------------------------------------------------------------------
1 | seed = $seed;
60 | $this->tokenResolverFactory = $token_resolver_factory ?: new TokenResolverFactory();
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function autoload($class) {
67 | return $this->autoloadPaths($class);
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function setClassMap(array $class_map) {
74 | // Remove the leading \ from the class names.
75 | $unprefixed_class_map = array();
76 | foreach ($class_map as $class_name => $tokenized_path) {
77 | $unprefixed_class_map[static::unprefixClass($class_name)] = $tokenized_path;
78 | }
79 | $this->classMap = $unprefixed_class_map;
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function setPsrClassMap(array $class_map) {
86 | // Remove the leading \ from the partial namespaces.
87 | $unprefixed_class_map = array();
88 | foreach ($class_map as $psr => $psr_class_map) {
89 | $unprefixed_class_map[$psr] = array();
90 | foreach ($psr_class_map as $class_name => $tokenized_path) {
91 | $unprefixed_class_map[$psr][static::unprefixClass($class_name)] = $tokenized_path;
92 | }
93 | }
94 | $this->psrClassMap = $unprefixed_class_map;
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public function setSeed($seed) {
101 | $this->seed = $seed;
102 | }
103 |
104 | /**
105 | * Strips a class name of a preceding backslash if necessary.
106 | *
107 | * @param string $class
108 | * The class to prefix.
109 | *
110 | * @return string
111 | * The prefixed class.
112 | */
113 | protected static function unprefixClass($class) {
114 | if (strpos($class, '\\') !== 0) {
115 | return $class;
116 | }
117 | return substr($class, 1);
118 | }
119 |
120 | /**
121 | * Helper function to autoload path based files.
122 | *
123 | * @param string $class
124 | * The requested class.
125 | *
126 | * @return bool
127 | * TRUE if the class was found. FALSE otherwise.
128 | */
129 | protected function autoloadPaths($class) {
130 | $class = static::unprefixClass($class);
131 | // If the class that PHP is trying to find is not in the class map, built
132 | // from the composer configuration, then bail.
133 | if (!in_array($class, array_keys($this->classMap))) {
134 | return FALSE;
135 | }
136 | try {
137 | $resolver = $this->tokenResolverFactory->factory($this->classMap[$class]);
138 | $finder = $resolver->resolve();
139 | // Have the path finder require the file and return TRUE or FALSE if it
140 | // found the file or not.
141 | $finder->requireFile($this->seed);
142 | return TRUE;
143 | }
144 | catch (ClassLoaderException $e) {
145 | // If there was an error, inform PHP that the class could not be found.
146 | return FALSE;
147 | }
148 | }
149 |
150 | /**
151 | * {@inheritdoc}
152 | */
153 | public function registerPsr(\Composer\Autoload\ClassLoader $loader) {
154 | // Composer's autoloader uses a different method to add each PSR partial
155 | // namespace.
156 | $psrs = array(
157 | 'psr-0' => 'add',
158 | 'psr-4' => 'addPsr4',
159 | );
160 | foreach ($psrs as $psr => $loader_method) {
161 | // The $psrClassMap contains an array for psr-0 and an array for psr-4.
162 | // Each one of those contains an array where the keys are the partial
163 | // namespace, and the value is an array of tokenized paths where those
164 | // partial namespaces can be found.
165 | // Ex:
166 | // [
167 | // 'psr-0' => [
168 | // [ 'Drupal\\plug\\' => ['DRUPAL_CONTRIB/lib', …] ],
169 | // ],
170 | // ]
171 | foreach ($this->psrClassMap[$psr] as $partial_namespace => $tokenized_paths) {
172 | if (!is_array($tokenized_paths)) {
173 | // If a string was passed, then convert it to an array for
174 | // consistency.
175 | $tokenized_paths = array($tokenized_paths);
176 | }
177 | foreach ($tokenized_paths as $tokenized_path) {
178 | try {
179 | // Find the real path for the tokenized one.
180 | $resolver = $this->tokenResolverFactory->factory($tokenized_path);
181 | $finder = $resolver->resolve();
182 | // Get the real path of the prefix.
183 | $real_path = $finder->find($this->seed);
184 | $loader->{$loader_method}($partial_namespace, $real_path);
185 | }
186 | catch (ClassLoaderException $e) {}
187 | }
188 | }
189 | }
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/src/LoaderInterface.php:
--------------------------------------------------------------------------------
1 | 'Drupal\Composer\ClassLoader\Discovery\PathFinderCore',
29 | 'DRUPAL_CONTRIB' => 'Drupal\Composer\ClassLoader\Discovery\PathFinderContrib',
30 | );
31 |
32 | /**
33 | * Constructs a TokenResolver object.
34 | *
35 | * @param string $tokenized_path
36 | * The path containing a potential token.
37 | */
38 | public function __construct($tokenized_path = '') {
39 | $this->path = $tokenized_path;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function resolve() {
46 | // If the path is not tokenized, then return the NULL discovery object.
47 | if (file_exists($this->path)) {
48 | return new Discovery\PathFinderNull(array($this->path));
49 | }
50 | if (!$this->getToken()) {
51 | return NULL;
52 | }
53 | // Get the name of the class of the PathFinder depending on if we are
54 | // looking for Drupal root or contrib path.
55 | $class_name = $this->getClassName();
56 | // Remove the leading token from the tokenized path to get the relative path
57 | // (to the Drupal root or the module/theme path).
58 | $arguments[] = $this->cleanToken();
59 | // Add more arguments to the constructor, like the module name.
60 | $arguments = array_merge($arguments, $this->parseArguments());
61 | return new $class_name($arguments);
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function hasToken() {
68 | return (bool) $this->getToken();
69 | }
70 |
71 | /**
72 | * {@inheritdoc}
73 | */
74 | public function trimPath() {
75 | if (!$token = $this->getToken()) {
76 | return $this->path;
77 | }
78 | $pos = strpos($this->path, $token);
79 | return substr($this->path, $pos);
80 | }
81 |
82 | /**
83 | * Removes the token from the tokenized path.
84 | *
85 | * @throws ClassLoaderException
86 | * If no token can be found.
87 | *
88 | * @return string
89 | * The cleaned path.
90 | */
91 | protected function cleanToken() {
92 | if ($token_name = $this->getToken()) {
93 | // Remove the token and arguments and return the path.
94 | $path = substr($this->path, strlen($token_name));
95 | return preg_replace('/<.*>/', '', $path);
96 | }
97 | $message = sprintf('No token could be found in "%s". Available tokens are: %s.', $this->path, implode(', ', array_keys($this->supportedTokens)));
98 | throw new ClassLoaderException($message);
99 | }
100 |
101 | /**
102 | * Checks if the current tokenized path contains a known token.
103 | *
104 | * @return string
105 | * The token found. NULL otherwise.
106 | */
107 | protected function getToken() {
108 | // Iterate over the supported tokens to find the token in the tokenized
109 | // path.
110 | foreach (array_keys($this->supportedTokens) as $token_name) {
111 | if (strpos($this->path, $token_name) !== FALSE) {
112 | return $token_name;
113 | }
114 | }
115 | return NULL;
116 | }
117 |
118 | /**
119 | * Gets the class name corresponding to the token.
120 | *
121 | * @return string
122 | * The class name.
123 | */
124 | protected function getClassName() {
125 | $token_name = $this->getToken();
126 | return $token_name ? $this->supportedTokens[$token_name] : NULL;
127 | }
128 |
129 | /**
130 | * Gets the arguments in the token.
131 | *
132 | * @return string[]
133 | * A numeric array containing the token arguments.
134 | */
135 | protected function parseArguments() {
136 | $token_name = $this->getToken();
137 | $delimiter = '/';
138 | $matches = array();
139 | if (preg_match($delimiter . preg_quote($token_name) . '<(.+)>.*' . $delimiter, $this->path, $matches)) {
140 | // Some arguments were found.
141 | return explode(',', $matches[1]);
142 | }
143 | return array();
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/src/TokenResolverFactory.php:
--------------------------------------------------------------------------------
1 | /src/",
12 | "": "DRUPAL_ROOT/includes"
13 | },
14 | "psr-0": {
15 | "": "DRUPAL_ROOT/includes"
16 | },
17 | "class-location": {
18 | "\\Tmp": "DRUPAL_ROOT/file.inc",
19 | "\\Tmp2": "data/acme.inc"
20 | }
21 | },
22 | "autoload-dev": {
23 | "psr-0": {
24 | "Drupal\\MyModule\\": "DRUPAL_CONTRIB/lib/"
25 | },
26 | "class-location": {
27 | "\\Tmp3": "data/acme.inc"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/data/acme.inc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./src/
7 |
8 |
9 |
10 |
11 |
12 | ./vendor
13 |
14 |
15 | ../src
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/run-coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # @file
3 | # Simple script to load composer and run the tests.
4 |
5 | set -e
6 |
7 | DIR=$(dirname $0)
8 | cd $DIR
9 | test -f "./vendor/bin/phpunit" || ./install.sh
10 | ./vendor/bin/phpunit --coverage-html ./report
11 |
--------------------------------------------------------------------------------
/tests/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # @file
3 | # Simple script to load composer and run the tests.
4 |
5 | set -e
6 |
7 | DIR=$(dirname $0)
8 | cd $DIR
9 | test -f "./vendor/bin/phpunit" || ./install.sh
10 | ./vendor/bin/phpunit "$@"
11 |
--------------------------------------------------------------------------------
/tests/src/AutoloaderBootstrapTest.php:
--------------------------------------------------------------------------------
1 | setAccessible(TRUE);
29 | $value = $reflection_property->getValue($autoloader);
30 | $this->assertEquals($loader, $value);
31 | $reflection_property = new \ReflectionProperty(get_class($autoloader), 'seed');
32 | $reflection_property->setAccessible(TRUE);
33 | $value = $reflection_property->getValue($autoloader);
34 | $this->assertEquals('composer.json', $value);
35 | }
36 |
37 | /**
38 | * Tests the ::register() method.
39 | *
40 | * @covers ::register()
41 | * @covers ::load()
42 | * @covers ::registerDrupalPaths()
43 | * @covers ::registerPsr()
44 | */
45 | public function test_register() {
46 | $loader = m::mock('\Composer\Autoload\ClassLoader');
47 | $loader
48 | ->shouldReceive('add')
49 | ->once();
50 | $loader
51 | ->shouldReceive('addPsr4')
52 | ->once();
53 | $loader
54 | ->shouldReceive('getPrefixes')
55 | ->once()
56 | ->andReturn([
57 | '' => 'DRUPAL_ROOT/includes',
58 | ]);
59 | $loader
60 | ->shouldReceive('getPrefixesPsr4')
61 | ->once()
62 | ->andReturn([
63 | 'Drupal\\Composer\\ClassLoader\\' => '../src/',
64 | 'Drupal\\Composer\\ClassLoader\\Tests\\' => 'src/',
65 | '' => 'DRUPAL_ROOT/includes',
66 | ]);
67 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
68 | $autoloader->register();
69 |
70 | $this->assertTrue($autoloader->checkLoadedAutoloader());
71 | // Make sure that calling to register a second time does not fail.
72 | $autoloader->register();
73 | $this->assertTrue($autoloader->checkLoadedAutoloader());
74 | }
75 |
76 | /**
77 | * Tests the ::checkLoadedAutoloader() method.
78 | */
79 | public function test_checkLoadedAutoloader() {
80 | $class_loader = m::mock('\Composer\Autoload\ClassLoader');
81 | $loader = m::mock('\Drupal\Composer\ClassLoader\LoaderInterface');
82 | $loader
83 | ->shouldReceive(AutoloaderBootstrap::AUTOLOAD_METHOD);
84 | $autoloader = new AutoloaderBootstrap($class_loader, 'data/docroot/sites/all/modules/testmodule/composer.json', $loader);
85 | $this->assertFalse($autoloader->checkLoadedAutoloader());
86 | spl_autoload_register(array($loader, AutoloaderBootstrap::AUTOLOAD_METHOD));
87 | $this->assertTrue($autoloader->checkLoadedAutoloader());
88 | }
89 |
90 | /**
91 | * Tests the protected ::unload() method.
92 | *
93 | * @covers ::unload()
94 | */
95 | public function test_unload() {
96 | $loader = m::mock('\Composer\Autoload\ClassLoader');
97 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
98 |
99 | // First load it.
100 | $reflection_method = new \ReflectionMethod(get_class($autoloader), 'load');
101 | $reflection_method->setAccessible(TRUE);
102 | $reflection_method->invoke($autoloader);
103 |
104 | // Make sure it's added
105 | $this->assertTrue($autoloader->checkLoadedAutoloader());
106 |
107 | // Then unload it.
108 | $reflection_method = new \ReflectionMethod(get_class($autoloader), 'unload');
109 | $reflection_method->setAccessible(TRUE);
110 | $reflection_method->invoke($autoloader);
111 |
112 | // Make sure it's not added
113 | $this->assertFalse($autoloader->checkLoadedAutoloader());
114 | }
115 |
116 | /**
117 | * Tests the ::registerDrupalPaths() method.
118 | *
119 | * @covers ::registerDrupalPaths()
120 | */
121 | public function test_registerDrupalPaths_empty() {
122 | $loader = m::mock('\Composer\Autoload\ClassLoader');
123 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
124 | $reflection_method = new \ReflectionMethod(get_class($autoloader), 'registerDrupalPaths');
125 | $reflection_method->setAccessible(TRUE);
126 | $value = $reflection_method->invokeArgs($autoloader, [[]]);
127 | $this->assertNull($value);
128 | }
129 |
130 | /**
131 | * Tests the ::registerPsr() method.
132 | *
133 | * @covers ::registerPsr()
134 | */
135 | public function test_registerPsr_empty() {
136 | $loader = m::mock('\Composer\Autoload\ClassLoader');
137 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
138 | $reflection_method = new \ReflectionMethod(get_class($autoloader), 'registerPsr');
139 | $reflection_method->setAccessible(TRUE);
140 | $value = $reflection_method->invokeArgs($autoloader, [[]]);
141 | $this->assertNull($value);
142 | }
143 |
144 | /**
145 | * Tests the ::getConfig method.
146 | *
147 | * @covers ::getConfig()
148 | */
149 | public function test_getConfig() {
150 | $loader = m::mock('\Composer\Autoload\ClassLoader');
151 | $loader
152 | ->shouldReceive('getPrefixes')
153 | ->once()
154 | ->andReturn([
155 | '' => '/lorem/ipsum/DRUPAL_ROOT/includes',
156 | ]);
157 | $loader
158 | ->shouldReceive('getPrefixesPsr4')
159 | ->once()
160 | ->andReturn([
161 | 'Drupal\\Composer\\ClassLoader\\' => '/lorem/ipsum/../src/',
162 | 'Drupal\\Composer\\ClassLoader\\Tests\\' => '/lorem/ipsum/src/',
163 | 'Drupal\\MyModule\\' => '/lorem/ipsum/DRUPAL_CONTRIB/src/',
164 | '' => '/lorem/ipsum/DRUPAL_ROOT/includes',
165 | ]);
166 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
167 | $config = $autoloader->getConfig();
168 | $expected = [
169 | 'psr-0' => [
170 | '' => ['DRUPAL_ROOT/includes'],
171 | ],
172 | 'psr-4' => [
173 | 'Drupal\\MyModule\\' => ['DRUPAL_CONTRIB/src/'],
174 | '' => ['DRUPAL_ROOT/includes'],
175 | ],
176 | 'class-location' => [
177 | '\\Tmp' => 'DRUPAL_ROOT/file.inc',
178 | '\\Tmp2' => 'data/acme.inc',
179 | '\\Tmp3' => 'data/acme.inc',
180 | ],
181 | ];
182 | $this->assertEquals($expected, $config);
183 | }
184 |
185 | /**
186 | * Tests the ::getClassLoader method.
187 | *
188 | * @covers ::getClassLoader()
189 | */
190 | public function test_getClassLoader() {
191 | $loader = m::mock('\Composer\Autoload\ClassLoader');
192 | $autoloader = new AutoloaderBootstrap($loader, 'data/docroot/sites/all/modules/testmodule/composer.json');
193 | $this->assertEquals($loader, $autoloader->getClassLoader());
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/tests/src/Discovery/PathFinderBaseTest.php:
--------------------------------------------------------------------------------
1 | setAccessible(true);
30 | $value = $property->getValue($pathFinder);
31 | $this->assertEquals('./testFolder/', $value);
32 | }
33 |
34 | /**
35 | * Tests that PathFinderBase::requireFile() works properly.
36 | *
37 | * @covers ::requireFile()
38 | */
39 | public function testRequireFile() {
40 | $pathFinder = new PathFinderNull(['data/acme.inc']);
41 | $pathFinder->requireFile('data/acme.inc');
42 | $included = get_included_files();
43 | $this->assertTrue(in_array(realpath('data/acme.inc'), $included));
44 | }
45 |
46 | /**
47 | * Tests that PathFinderBase::cleanDirPath() works properly.
48 | * @dataProvider cleanDirPathProvider
49 | *
50 | * @covers ::cleanDirPath()
51 | */
52 | public function testCleanDirPath($given, $expected) {
53 | $pathFinder = new PathFinderNull(['']);
54 |
55 | $class = new \ReflectionClass('\Drupal\Composer\ClassLoader\Discovery\PathFinderBase');
56 | $method = $class->getMethod('cleanDirPath');
57 | $method->setAccessible(true);
58 | $output = $method->invokeArgs($pathFinder, [$given]);
59 |
60 | $this->assertEquals($expected, $output);
61 | }
62 |
63 | /**
64 | * Provider for testCleanDirPath.
65 | */
66 | public static function cleanDirPathProvider() {
67 | return array(
68 | array('./testFolder/.', './testFolder'),
69 | array('./testFolder', './testFolder'),
70 | array('./deep/testFolder/.', './deep/testFolder'),
71 | array('testFolder/.', 'testFolder'),
72 | array('deep/testFolder/.', 'deep/testFolder'),
73 | );
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/tests/src/Discovery/PathFinderContribTest.php:
--------------------------------------------------------------------------------
1 | setAccessible(true);
31 | $value = $property->getValue($pathFinder);
32 | $this->assertEquals('./testFolder/', $value);
33 |
34 | // Assert module name is set.
35 | $property = new \ReflectionProperty($pathFinder, 'moduleName');
36 | $property->setAccessible(true);
37 | $value = $property->getValue($pathFinder);
38 | $this->assertEquals('mymodule', $value);
39 |
40 | // Assert path finder is set.
41 | $property = new \ReflectionProperty($pathFinder, 'coreFinder');
42 | $property->setAccessible(true);
43 | $value = $property->getValue($pathFinder);
44 | $this->assertInstanceOf('\Drupal\Composer\ClassLoader\Discovery\PathFinderInterface', $value);
45 | }
46 |
47 | /**
48 | * Tests that find() works properly.
49 | *
50 | * @covers ::find()
51 | */
52 | public function testFind() {
53 | // 1. Test successful path.
54 | $pathFinder = new PathFinderContrib(['', 'testmodule']);
55 | $path = $pathFinder->find('data/docroot/sites/all/modules/testmodule/composer.json');
56 | $this->assertEquals(realpath('data/docroot/sites/all/modules/testmodule'), $path);
57 | }
58 |
59 | /**
60 | * Tests that ::find() works properly.
61 | *
62 | * @expectedException \Drupal\Composer\ClassLoader\ClassLoaderException
63 | *
64 | * @covers ::find()
65 | */
66 | public function test_find__noDrupal() {
67 | // 2. Test seed not in Drupal root.
68 | $pathFinder = new PathFinderContrib(['data/acme.inc', 'testmodule']);
69 | $pathFinder->find(__DIR__);
70 | }
71 |
72 | /**
73 | * Tests that ::find() works properly.
74 | *
75 | * @expectedException \Drupal\Composer\ClassLoader\ClassLoaderException
76 | *
77 | * @covers ::find()
78 | */
79 | public function test_find__noContrib() {
80 | // 3. Test seed not in Drupal contrib.
81 | $coreFinder = m::mock('\Drupal\Composer\ClassLoader\Discovery\PathFinderCore');
82 | $coreFinder->shouldReceive('find')->andReturn('data/docroot/')->once();
83 | $pathFinder = new PathFinderContrib(['data/docroot/sites/all/modules/testmodule/composer.json', 'testmodule2'], $coreFinder);
84 | $pathFinder->find(__DIR__);
85 | }
86 |
87 | /**
88 | * Tests that ::isWantedContrib() works properly.
89 | *
90 | * @covers ::isWantedContrib()
91 | */
92 | public function test_isWantedContrib() {
93 | $pathFinder = new PathFinderContrib(['', 'testmodule']);
94 | $dir = new \SplFileInfo('data/docroot/sites/all/modules/testmodule');
95 |
96 | $reflection_object = new \ReflectionObject($pathFinder);
97 | $method = $reflection_object->getMethod('isWantedContrib');
98 | $method->setAccessible(true);
99 | $output = $method->invokeArgs($pathFinder, [$dir]);
100 |
101 | $this->assertTrue($output);
102 | }
103 |
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/tests/src/Discovery/PathFinderCoreTest.php:
--------------------------------------------------------------------------------
1 | find('data/docroot/sites/all/modules/testmodule/composer.json');
31 |
32 | $this->assertEquals(realpath('data/docroot/file.inc'), $found);
33 | }
34 |
35 | /**
36 | * Tests that ::find() works properly.
37 | *
38 | * @expectedException \Drupal\Composer\ClassLoader\ClassLoaderException
39 | *
40 | * @covers ::getParentDirectory()
41 | */
42 | public function test_getParentDirectory__noParent() {
43 | // 2. Test seed not in Drupal root.
44 | $pathFinder = new PathFinderCore(['data/acme.inc']);
45 | $pathFinder->find('file.inc');
46 | }
47 |
48 | /**
49 | * Tests that ::find() works properly.
50 | *
51 | * @expectedException \Drupal\Composer\ClassLoader\ClassLoaderException
52 | *
53 | * @covers ::getParentDirectory()
54 | */
55 | public function test_getParentDirectory__root() {
56 | // 3. Test seed in Drupal root throws exception.
57 | $pathFinder = new PathFinderCore(['/']);
58 | $class = new \ReflectionClass('\Drupal\Composer\ClassLoader\Discovery\PathFinderCore');
59 | $method = $class->getMethod('getParentDirectory');
60 | $method->setAccessible(true);
61 | $output = $method->invokeArgs($pathFinder, ['/']);
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/tests/src/Discovery/PathFinderNullTest.php:
--------------------------------------------------------------------------------
1 | find(NULL);
29 | $this->assertEquals('data/acme.inc', $path);
30 | }
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/tests/src/LoaderTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('requireFile')
31 | ->once();
32 | // Mock a resolver object.
33 | $resolver = m::mock('\Drupal\Composer\ClassLoader\TokenResolverInterface');
34 | $resolver
35 | ->shouldReceive('resolve')
36 | ->once()
37 | ->andReturn($finder);
38 | // Mock a resolver factory object.
39 | $resolver_factory = m::mock('\Drupal\Composer\ClassLoader\TokenResolverFactoryInterface');
40 | $resolver_factory
41 | ->shouldReceive('factory')
42 | ->once()
43 | ->andReturn($resolver);
44 |
45 | $loader = new Loader(__DIR__, $resolver_factory);
46 | $loader->setClassMap([
47 | '\\Acme' => './data/acme.inc',
48 | ]);
49 | $this->assertTrue($loader->autoload('Acme'));
50 | }
51 |
52 | /**
53 | * Tests that Loader::autoload() works properly.
54 | *
55 | * @covers ::autoload()
56 | * @covers ::autoloadPaths()
57 | */
58 | public function test_autoload_unexisting() {
59 | $loader = new Loader(__DIR__);
60 | $loader->setClassMap([
61 | '\\Acme' => './data/acme.inc',
62 | ]);
63 | $this->assertFalse($loader->autoload('Invalid'));
64 | }
65 |
66 | /**
67 | * Tests that Loader::autoload() works properly.
68 | *
69 | * @covers ::autoload()
70 | * @covers ::autoloadPaths()
71 | */
72 | public function test_autoload_finderException() {
73 | $loader = new Loader(__DIR__);
74 | $loader->setClassMap([
75 | '\\Acme' => 'DRUPAL_ROOT/file.inc',
76 | ]);
77 | $this->assertFalse($loader->autoload('Acme'));
78 | }
79 |
80 | /**
81 | * Tests that Loader::setClassMap works properly.
82 | *
83 | * @dataProvider setClassMapProvider
84 | *
85 | * @covers ::setClassMap()
86 | */
87 | public function test_setClassMap($given, $expected) {
88 | $loader = new Loader(__DIR__);
89 | $loader->setClassMap($given);
90 | $reflection_property = new \ReflectionProperty('\Drupal\Composer\ClassLoader\Loader', 'classMap');
91 | $reflection_property->setAccessible(TRUE);
92 | $value = $reflection_property->getValue($loader);
93 | $this->assertEquals($expected, $value);
94 | }
95 |
96 | /**
97 | * Provider for test_setClassMap.
98 | */
99 | public static function setClassMapProvider() {
100 | return [
101 | [['\\Foo' => 'bar'], ['Foo' => 'bar']],
102 | [['Foo' => 'bar'], ['Foo' => 'bar']],
103 | ];
104 | }
105 |
106 | /**
107 | * Tests that Loader::setPsrClassMap works properly.
108 | *
109 | * @dataProvider setPsrClassMapProvider
110 | *
111 | * @covers ::setPsrClassMap()
112 | */
113 | public function test_setPsrClassMap($given, $expected) {
114 | $loader = new Loader(__DIR__);
115 | $loader->setPsrClassMap($given);
116 | $reflection_property = new \ReflectionProperty('\Drupal\Composer\ClassLoader\Loader', 'psrClassMap');
117 | $reflection_property->setAccessible(TRUE);
118 | $value = $reflection_property->getValue($loader);
119 | $this->assertEquals($expected, $value);
120 | }
121 |
122 | /**
123 | * Provider for test_setClassMap.
124 | */
125 | public static function setPsrClassMapProvider() {
126 | return [
127 | [
128 | ['psr-4' => ['\\Foo' => 'bar'], 'psr-0' => ['\\Baz' => 'oof']],
129 | ['psr-4' => ['Foo' => 'bar'], 'psr-0' => ['Baz' => 'oof']]
130 | ],
131 | [
132 | ['psr-4' => ['Foo' => 'bar'], 'psr-0' => ['Baz' => 'oof']],
133 | ['psr-4' => ['Foo' => 'bar'], 'psr-0' => ['Baz' => 'oof']]
134 | ],
135 | ];
136 | }
137 |
138 | /**
139 | * Tests that Loader::setSeed works properly.
140 | *
141 | * @covers ::setSeed()
142 | */
143 | public function test_setSeed() {
144 | $loader = new Loader('Lorem ipsum');
145 | $seed = 'Dolor sit';
146 | $loader->setSeed($seed);
147 | $reflection_property = new \ReflectionProperty('\Drupal\Composer\ClassLoader\Loader', 'seed');
148 | $reflection_property->setAccessible(TRUE);
149 | $value = $reflection_property->getValue($loader);
150 | $this->assertEquals($seed, $value);
151 | }
152 |
153 | /**
154 | * Tests that Loader::registerPsr works properly.
155 | *
156 | * @covers ::registerPsr()
157 | */
158 | public function test_registerPsr() {
159 | $loader = new Loader('data/docroot/sites/all/modules/testmodule/composer.json');
160 | // Mock the \Composer\Autoload\ClassLoader loader.
161 | $classLoader = m::mock('\Composer\Autoload\ClassLoader');
162 | $classLoader
163 | ->shouldReceive('add')
164 | ->twice();
165 | $classLoader
166 | ->shouldReceive('addPsr4')
167 | ->once();
168 |
169 | $psrClassMap = [
170 | 'psr-0' => [
171 | 'Drupal\\plug\\' => [
172 | 'DRUPAL_CONTRIB/testmodule.info',
173 | 'DRUPAL_ROOT/file.inc'
174 | ],
175 | ],
176 | 'psr-4' => [
177 | 'Drupal\\Kitten\\' => 'DRUPAL_ROOT/file.inc',
178 | ],
179 | ];
180 | $loader->setPsrClassMap($psrClassMap);
181 | $loader->registerPsr($classLoader);
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/tests/src/TokenResolverFactoryTest.php:
--------------------------------------------------------------------------------
1 | factory('Lorem');
28 | $this->assertInstanceOf('\Drupal\Composer\ClassLoader\TokenResolverInterface', $resolver);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/tests/src/TokenResolverTest.php:
--------------------------------------------------------------------------------
1 | setAccessible(TRUE);
29 | $value = $reflection_property->getValue($resolver);
30 | $this->assertEquals($tokenized_path, $value);
31 | }
32 |
33 | /**
34 | * Tests that ::resolve() is working.
35 | *
36 | * @dataProvider resolveProvider
37 | *
38 | * @covers ::resolve()
39 | * @covers ::cleanToken()
40 | * @covers ::getToken()
41 | * @covers ::getClassName()
42 | * @covers ::parseArguments()
43 | */
44 | public function test_resolve($path, $expected) {
45 | $resolver = new TokenResolver($path);
46 | $finder = $resolver->resolve();
47 | $this->assertInstanceOf($expected, $finder);
48 | }
49 |
50 | /**
51 | * Provider method for test_resolve.
52 | */
53 | public static function resolveProvider() {
54 | return [
55 | // 1. Path not tokenized.
56 | ['data/acme.inc', '\Drupal\Composer\ClassLoader\Discovery\PathFinderNull'],
57 | // 2. Core path.
58 | ['DRUPAL_ROOT/file.inc', '\Drupal\Composer\ClassLoader\Discovery\PathFinderCore'],
59 | // 3. Contrib path.
60 | ['DRUPAL_CONTRIB/testmodule.info', '\Drupal\Composer\ClassLoader\Discovery\PathFinderContrib'],
61 | ];
62 | }
63 |
64 | /**
65 | * Tests that ::resolve() is working.
66 | *
67 | * @covers ::resolve()
68 | * @covers ::cleanToken()
69 | * @covers ::getToken()
70 | * @covers ::getClassName()
71 | * @covers ::parseArguments()
72 | */
73 | public function test_resolve_unexisting() {
74 | // 4. Unexisting path without a real token.
75 | $resolver = new TokenResolver('Lorem');
76 | $finder = $resolver->resolve();
77 | $this->assertNull($finder);
78 | }
79 |
80 | /**
81 | * Tests that the ::cleanToken is working.
82 | *
83 | * @expectedException \Drupal\Composer\ClassLoader\ClassLoaderException
84 | *
85 | * @covers ::cleanToken()
86 | */
87 | public function test_cleanToken() {
88 | $resolver = new TokenResolver('');
89 | $reflection_method = new \ReflectionMethod(get_class($resolver), 'cleanToken');
90 | $reflection_method->setAccessible(TRUE);
91 | $reflection_method->invoke($resolver);
92 | }
93 |
94 | /**
95 | * Tests that the ::cleanToken is working.
96 | *
97 | * @dataProvider hasTokenProvider
98 | *
99 | * @covers ::hasToken()
100 | */
101 | public function test_hasToken($path, $expected) {
102 | $resolver = new TokenResolver($path);
103 | $this->assertEquals($resolver->hasToken(), $expected);
104 | }
105 |
106 | /**
107 | * Provider method for test_hasToken.
108 | */
109 | public function hasTokenProvider() {
110 | return [
111 | ['DRUPAL_ROOT/includes', TRUE],
112 | ['DRUPAL_CONTRIB/src/', TRUE],
113 | ['/lorem/ipsum/DRUPAL_ROOT/src/', TRUE],
114 | ['/lorem/ipsum/DRUPAL_CONTRIB/src/', TRUE],
115 | ['src', FALSE],
116 | ['/lorem/ipsum/src', FALSE],
117 | ];
118 | }
119 |
120 | /**
121 | * Tests that the ::cleanToken is working.
122 | *
123 | * @dataProvider trimPathProvider
124 | *
125 | * @covers ::trimPath()
126 | */
127 | public function test_trimPath($path, $expected) {
128 | $resolver = new TokenResolver($path);
129 | $this->assertEquals($resolver->trimPath(), $expected);
130 | }
131 |
132 | /**
133 | * Provider method for test_trimPath.
134 | */
135 | public function trimPathProvider() {
136 | return [
137 | ['DRUPAL_ROOT/includes', 'DRUPAL_ROOT/includes'],
138 | ['DRUPAL_CONTRIB/src/', 'DRUPAL_CONTRIB/src/'],
139 | ['/lorem/ipsum/DRUPAL_ROOT/includes/', 'DRUPAL_ROOT/includes/'],
140 | ['/lorem/ipsum/DRUPAL_CONTRIB/src/', 'DRUPAL_CONTRIB/src/'],
141 | ['src', 'src'],
142 | [NULL, NULL],
143 | ];
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------