Drupal: add missing modules

This commit is contained in:
Robert 2021-01-08 11:55:00 +01:00
parent 3dcf246bda
commit ad550e0afe
185 changed files with 14408 additions and 0 deletions

View File

@ -0,0 +1,5 @@
language: php
php:
- 5.5
- 5.4
- 5.3

View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,3 @@
[![Build Status](https://travis-ci.org/donquixote/drupal-xautoload.png)](https://travis-ci.org/donquixote/drupal-xautoload)

View File

@ -0,0 +1,17 @@
<?php
/**
* This class is not used anywhere in xautoload, but could be used by other
* modules.
*/
class xautoload_FinderPlugin_CheckIncludePath implements xautoload_FinderPlugin_Interface {
/**
* {@inheritdoc}
*/
function findFile($api, $logical_base_path, $relative_path) {
$path = $logical_base_path . $relative_path;
return $api->suggestFile_checkIncludePath($path);
}
}

View File

@ -0,0 +1,51 @@
<?php
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
use Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface;
/**
* X Autoload plugins are for:
* - More exotic autoload patterns that are incompatible with PSR-0 or PEAR
* - Situations where we don't want to register a ton of namespaces, and using
* a plugin instead gives us performance benefits.
*/
interface xautoload_FinderPlugin_Interface extends DirectoryBehaviorInterface {
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* E.g.:
* - The class we look for is Some\Namespace\Some\Class
* - The file is actually in "exotic/location.php". This is not following
* PSR-0 or PEAR standard, so we need a plugin.
* -> The class finder will transform the class name to
* "Some/Namespace/Some/Class.php"
* - The plugin was registered for the namespace "Some\Namespace". This is
* because all those exotic classes all begin with Some\Namespace\
* -> The arguments will be:
* ($api = the API object, see below)
* $logical_base_path = "Some/Namespace/"
* $relative_path = "Some/Class.php"
* $api->getClass() gives the original class name, if we still need it.
* -> We are supposed to:
* if ($api->suggestFile('exotic/location.php')) {
* return TRUE;
* }
*
* @param InjectedApiInterface $api
* An object with a suggestFile() method.
* We are supposed to suggest files until suggestFile() returns TRUE, or we
* have no more suggestions.
* @param string $logical_base_path
* The key that this plugin was registered with.
* With trailing '/'.
* @param string $relative_path
* Second part of the canonical path, ending with '.php'.
*
* @return bool|null
* TRUE, if the file was found.
* FALSE or NULL, otherwise.
*/
function findFile($api, $logical_base_path, $relative_path);
}

View File

@ -0,0 +1,248 @@
<?php
use Drupal\xautoload\Adapter\LocalDirectoryAdapter;
use Drupal\xautoload\Util;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
/**
* An instance of this class is passed around to implementations of
* hook_xautoload(). It acts as a wrapper around the ClassFinder, to register
* stuff.
*
* Most of the methods here are deprecated. You should use the methods inherited
* from the base class, LocalDirectoryAdapter, instead.
*/
class xautoload_InjectedAPI_hookXautoload extends LocalDirectoryAdapter {
/**
* @var ExtendedClassFinderInterface
*/
protected $finder;
/**
* @param ExtendedClassFinderInterface $finder
* The class finder object.
* @param string $localDirectory
*
* @return self
*/
static function create($finder, $localDirectory) {
$adapter = ClassFinderAdapter::create($finder);
return new self($adapter, $localDirectory);
}
/**
* @param ClassFinderAdapter $adapter
* The class finder object.
* @param string $localDirectory
*/
function __construct($adapter, $localDirectory) {
parent::__construct($adapter, $localDirectory);
$this->finder = $adapter->getFinder();
}
// Prefix stuff
// ---------------------------------------------------------------------------
/**
* Register an additional prefix for this module.
* Note: Drupal\<module name>\ is already registered for <module dir>/lib.
*
* @deprecated
*
* @param string $prefix
* The prefix.
* @param string $prefix_root_dir
* Prefix root dir.
* If $relative is TRUE, this is relative to the extension module dir.
* If $relative is FALSE, this is an absolute path.
* @param boolean $relative
* Whether or not the path is relative to the current extension dir.
*/
function prefixRoot($prefix, $prefix_root_dir = NULL, $relative = TRUE) {
$prefix_root_dir = $this->processDir($prefix_root_dir, $relative);
$this->finder->registerPrefixRoot($prefix, $prefix_root_dir);
}
/**
* Register an additional namespace for this module.
* Note: Drupal\<module name>\ is already registered for <module dir>/lib.
*
* @deprecated
*
* @param string $prefix
* The namespace
* @param string $prefix_deep_dir
* PSR-0 root dir.
* If $relative is TRUE, this is relative to the current extension dir.
* If $relative is FALSE, this is an absolute path.
* @param boolean $relative
* Whether or not the path is relative to the current extension dir.
*/
function prefixDeep($prefix, $prefix_deep_dir = NULL, $relative = TRUE) {
$prefix_deep_dir = $this->processDir($prefix_deep_dir, $relative);
$this->finder->registerPrefixDeep($prefix, $prefix_deep_dir);
}
/**
* Legacy: Plugins were called "Handler" before.
*
* @deprecated
*
* @param string $prefix
* @param xautoload_FinderPlugin_Interface $plugin
*
* @return string
* The key under which the plugin was registered. This can later be used to
* unregister the plugin again.
*/
function prefixHandler($prefix, $plugin) {
$key = Util::randomString();
$this->finder->registerPrefixDeep($prefix, $key, $plugin);
return $key;
}
/**
* Register a prefix plugin object
*
* @deprecated
*
* @param string $prefix
* @param xautoload_FinderPlugin_Interface $plugin
*
* @return string
* The key under which the plugin was registered. This can later be used to
* unregister the plugin again.
*/
function prefixPlugin($prefix, $plugin) {
$key = Util::randomString();
$this->finder->registerPrefixDeep($prefix, $key, $plugin);
return $key;
}
// Namespace stuff
// ---------------------------------------------------------------------------
/**
* Register an additional namespace for this module.
* Note: Drupal\<module name>\ is already registered for <module dir>/lib.
*
* @deprecated
*
* @param string $namespace
* The namespace
* @param string $psr_0_root_dir
* PSR-0 root dir.
* If $relative is TRUE, this is relative to the current module dir.
* If $relative is FALSE, this is an absolute path.
* @param boolean $relative
* Whether or not the path is relative to the current extension dir.
*/
function namespaceRoot($namespace, $psr_0_root_dir = NULL, $relative = TRUE) {
$psr_0_root_dir = $this->processDir($psr_0_root_dir, $relative);
$this->finder->registerNamespaceRoot($namespace, $psr_0_root_dir);
}
/**
* Register an additional namespace for this module.
* Note: Drupal\<module name>\ is already registered for <module dir>/lib.
*
* @deprecated
*
* @param string $namespace
* The namespace
* @param string $namespace_deep_dir
* PSR-0 root dir.
* If $relative is TRUE, this is relative to the current extension dir.
* If $relative is FALSE, this is an absolute path.
* @param boolean $relative
* Whether or not the path is relative to the current extension dir.
*/
function namespaceDeep($namespace, $namespace_deep_dir = NULL, $relative = TRUE) {
$namespace_deep_dir = $this->processDir($namespace_deep_dir, $relative);
$this->finder->registerNamespaceDeep($namespace, $namespace_deep_dir);
}
/**
* Register a namespace plugin object
*
* @deprecated
*
* @param string $namespace
* @param xautoload_FinderPlugin_Interface $plugin
*
* @return string
* The key under which the plugin was registered. This can later be used to
* unregister the plugin again.
*/
function namespacePlugin($namespace, $plugin) {
$key = Util::randomString();
$this->finder->registerNamespaceDeep($namespace, $key, $plugin);
return $key;
}
/**
* Legacy: Plugins were called "Handler" before.
*
* @deprecated
*
* @param string $namespace
* @param xautoload_FinderPlugin_Interface $plugin
*
* @return string
* The key under which the plugin was registered. This can later be used to
* unregister the plugin again.
*/
function namespaceHandler($namespace, $plugin) {
$key = Util::randomString();
$this->finder->registerNamespaceDeep($namespace, $key, $plugin);
return $key;
}
/**
* Process a given directory to make it relative to Drupal root,
* instead of relative to the current extension dir.
*
* @deprecated
*
* @param string $dir
* The directory path that we want to make absolute.
* @param boolean $relative
* If TRUE, the $dir will be transformed from relative to absolute.
* If FALSE, the $dir is assumed to already be absolute, and remain unchanged.
*
* @return string
* The modified (absolute) directory path.
*/
protected function processDir($dir, $relative) {
if (!isset($dir)) {
return $this->localDirectory . 'lib/';
}
$dir = strlen($dir)
? rtrim($dir, '/') . '/'
: '';
return $relative
? $this->localDirectory . $dir
: $dir;
}
/**
* Explicitly set the base for relative paths.
*
* Alias for LocalDirectoryAdapter::setLocalDirectory()
*
* @param string $dir
* New relative base path.
*/
function setExtensionDir($dir) {
$this->localDirectory = strlen($dir)
? rtrim($dir, '/') . '/'
: '';
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Drupal\xautoload\Tests;
use Drupal\xautoload\Util;
class EnvironmentSnapshotMaker {
/**
* @var array
*/
protected static $snapshots = array();
/**
* @param string $module
* @param string $phase
* @param string[] $classes
*/
static function takeSnapshot($module, $phase, $classes) {
self::$snapshots[$module][$phase] = self::buildSnapshot($classes);
}
/**
* @param string $module
*
* @return array
*/
static function getSnapshots($module) {
return isset(self::$snapshots[$module])
? self::$snapshots[$module]
: array();
}
/**
* @param string[] $classes
*
* @return array
*/
protected static function buildSnapshot($classes) {
$observations = array();
// Test that all classes are available immediately at boot time.
foreach ($classes as $class) {
$observations['class_exists'][$class] = class_exists($class);
}
// Check variable_get().
$observations[XAUTOLOAD_VARNAME_CACHE_TYPES] = variable_get(XAUTOLOAD_VARNAME_CACHE_TYPES);
$observations[XAUTOLOAD_VARNAME_CACHE_LAZY] = variable_get(XAUTOLOAD_VARNAME_CACHE_LAZY);
$observations['db_connection_info'] = \Database::getConnectionInfo();
$spl_autoload_stack = array();
foreach (spl_autoload_functions() as $callback) {
$spl_autoload_stack[] = Util::callbackToString($callback);
}
$observations['spl_autoload_functions'] = $spl_autoload_stack;
return $observations;
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace Drupal\xautoload\Tests;
use Drupal\xautoload\ClassFinder\ClassFinder;
use Drupal\xautoload\ClassFinder\InjectedApi\CollectFilesInjectedApi;
use Drupal\xautoload\ClassLoader\ClassLoaderInterface;
use Drupal\xautoload\Util;
class XAutoloadUnitTestCase extends \DrupalUnitTestCase {
static function getInfo() {
return array(
'name' => 'X Autoload unit test',
'description' => 'Test the xautoload class finder.',
'group' => 'X Autoload',
);
}
function setUp() {
// drupal_load('module', 'xautoload') would register namespaces for all
// enabled modules, which is not intended for this unit test.
// Instead, we just include xautoload.early.inc.
require_once __DIR__ . '/../../../../xautoload.early.inc';
// Make sure we use the regular loader, not the APC one.
// Also make sure to prepend this one. Otherwise, the core class loader will
// try to load xautoload-related stuff, e.g. xautoload_Mock_* stuff, and
// will fail due to the database.
foreach (spl_autoload_functions() as $callback) {
if (is_array($callback)
&& ($loader = $callback[0])
&& $loader instanceof ClassLoaderInterface
) {
$loader->unregister();
}
}
xautoload()->finder->register(TRUE);
// Do the regular setUp().
parent::setUp();
}
function testAutoloadStackOrder() {
$expected = array(
'Drupal\\xautoload\\ClassFinder\\ClassFinder->loadClass()',
/* @see _drupal_bootstrap_database() */
'drupal_autoload_class',
'drupal_autoload_interface',
/* @see simpletest_classloader_register() */
'_simpletest_autoload_psr4_psr0',
);
$actual = array();
foreach (spl_autoload_functions() as $callback) {
$actual[] = Util::callbackToString($callback);
}
$this->assertEqualBlock($expected, $actual, "SPL autoload stack:");
}
function testNamespaces() {
// Prepare the class finder.
$finder = new ClassFinder();
$finder->add('Drupal\\ex_ample', 'sites/all/modules/contrib/ex_ample/lib-psr0');
$finder->addPsr4('Drupal\\ex_ample', 'sites/all/modules/contrib/ex_ample/lib-psr4');
// Test class finding for 'Drupal\\ex_ample\\Abc_Def'.
$this->assertFinderSuggestions($finder, 'Drupal\\ex_ample\\Abc_Def', array(
// Class finder is expected to suggest these files, in the exact order,
// until one of them is accepted.
array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib-psr0/Drupal/ex_ample/Abc/Def.php'),
array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib-psr4/Abc_Def.php'),
));
}
function testPrefixes() {
// Prepare the class finder.
$finder = new ClassFinder();
$finder->registerPrefixDeep('ex_ample', 'sites/all/modules/contrib/ex_ample/lib');
$finder->registerPrefixRoot('ex_ample', 'sites/all/modules/contrib/ex_ample/vendor');
// Test class finding for 'ex_ample_Abc_Def'.
$this->assertFinderSuggestions($finder, 'ex_ample_Abc_Def', array(
// Class finder is expected to suggest these files, in the exact order,
// until one of them is accepted.
array('suggestFile', 'sites/all/modules/contrib/ex_ample/lib/Abc/Def.php'),
array('suggestFile', 'sites/all/modules/contrib/ex_ample/vendor/ex/ample/Abc/Def.php'),
));
}
/**
* @param ClassFinder $finder
* @param string $class
* @param array $expectedSuggestions
*
* @return bool
* Result of the assertion
*/
protected function assertFinderSuggestions($finder, $class, array $expectedSuggestions) {
$success = TRUE;
for ($iAccept = 0; $iAccept < count($expectedSuggestions); ++$iAccept) {
list($method_name, $file) = $expectedSuggestions[$iAccept];
$api = new CollectFilesInjectedApi($class, $method_name, $file);
$finder->apiFindFile($api, $class);
$suggestions = $api->getSuggestions();
$expected = array_slice($expectedSuggestions, 0, $iAccept + 1);
$success = $success && $this->assertEqualBlock($expected, $suggestions, "Finder suggestions for class <code>$class</code>:");
}
return $success;
}
/**
* @param mixed $expected
* @param mixed $actual
* @param string $label
*
* @return bool
* Result of the assertion
*/
protected function assertEqualBlock($expected, $actual, $label) {
$label .= '<br/>' .
'Expected: <pre>' . var_export($expected, TRUE) . '</pre>' .
'Actual: <pre>' . var_export($actual, TRUE) . '</pre>';
return $this->assertEqual($expected, $actual, $label);
}
}

View File

@ -0,0 +1,264 @@
<?php
namespace Drupal\xautoload\Tests;
class XAutoloadWebTestCase extends \DrupalWebTestCase {
/**
* {@inheritdoc}
*/
static function getInfo() {
return array(
'name' => 'X Autoload web test',
'description' => 'Test xautoload class loading for an example module.',
'group' => 'X Autoload',
);
}
/**
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
}
/**
*
*/
function testNoCache() {
$this->xautoloadTestWithCacheTypes(array(), TRUE);
}
/**
*
*/
function testApcCache() {
$cache_types = array(
'apc' => 'apc',
'xcache' => 'xcache',
'wincache' => 'wincache',
);
$this->xautoloadTestWithCacheTypes($cache_types, TRUE);
}
/**
* @param array $cache_types
* The autoloader modes that are enabled, e.g.
* array('apc' => 'apc', 'xcache' => 'xcache')
* @param bool $cache_lazy
* Whether the "lazy" mode is enabled.
*/
protected function xautoloadTestWithCacheTypes($cache_types, $cache_lazy) {
variable_set(XAUTOLOAD_VARNAME_CACHE_TYPES, $cache_types);
$this->pass("Set cache types: " . var_export($cache_types, TRUE));
variable_set(XAUTOLOAD_VARNAME_CACHE_LAZY, $cache_lazy);
$this->pass("Set cache lazy mode: " . var_export($cache_lazy, TRUE));
// Enable xautoload.
module_enable(array('xautoload'), FALSE);
// At this time the xautoload_cache_mode setting is not in effect yet,
// so we have to clear old cached values from APC cache.
xautoload()->cacheManager->renewCachePrefix();
module_enable(array(
'xautoload_test_1',
'xautoload_test_2',
'xautoload_test_3',
'xautoload_test_4',
'xautoload_test_5',
), FALSE);
menu_rebuild();
foreach (array(
'xautoload_test_1' => array('Drupal\xautoload_test_1\ExampleClass'),
'xautoload_test_2' => array('xautoload_test_2_ExampleClass'),
'xautoload_test_3' => array('Drupal\xautoload_test_3\ExampleClass'),
) as $module => $classes) {
$classes_on_include = in_array($module, array('xautoload_test_2', 'xautoload_test_3'));
$this->xautoloadModuleEnabled($module, $classes, $classes_on_include);
$this->xautoloadModuleCheckJson($module, $cache_types, $cache_lazy, $classes);
}
}
/**
* @param string $module
* @param string[] $classes
* @param bool $classes_on_include
*/
protected function xautoloadModuleEnabled($module, $classes, $classes_on_include) {
EnvironmentSnapshotMaker::takeSnapshot($module, 'later', $classes);
$all = EnvironmentSnapshotMaker::getSnapshots($module);
foreach ($all as $phase => $observations) {
$when = ($phase === 'early')
? 'on drupal_load() during module_enable()'
: (($phase === 'later')
? 'after hook_modules_enabled()'
: 'at an undefined time'
);
// Test the classes of the example module.
foreach ($classes as $class) {
// Test that the class was already found in $phase.
$this->assertTrue(isset($observations['class_exists'][$class]), "Class $class was checked $when.");
if ($classes_on_include || $phase !== 'early') {
$this->assertTrue($observations['class_exists'][$class], "Class $class was found $when.");
}
else {
$this->assertFalse($observations['class_exists'][$class], "Class $class cannot be found $when.");
}
}
}
}
/**
* @param string $module
* @param array $cache_types
* The autoloader modes that are enabled, e.g.
* array('apc' => 'apc', 'xcache' => 'xcache')
* @param bool $cache_lazy
* Whether the "lazy" mode is enabled.
* @param string[] $classes
*/
protected function xautoloadModuleCheckJson($module, $cache_types, $cache_lazy, $classes) {
$path = "$module.json";
$json = $this->drupalGet($path);
$all = json_decode($json, TRUE);
if (!is_array($all) || empty($all)) {
$this->fail("$path must return a non-empty json array.");
return;
}
foreach ($all as $phase => $observations) {
$when = ($phase === 'early')
? 'on early bootstrap'
: (($phase === 'boot')
? 'during hook_boot()'
: 'at an undefined time'
);
$this->xautoloadCheckTestEnvironment($observations, $cache_types, $cache_lazy, $when);
// Test the classes of the example module.
foreach ($classes as $class) {
// Test that the class was already found in $phase.
$this->assertTrue($observations['class_exists'][$class], "Class $class was found $when.");
}
}
}
/**
* @param array $observations
* @param array $cache_types
* The autoloader modes that are enabled, e.g.
* array('apc' => 'apc', 'xcache' => 'xcache')
* @param bool $lazy
* Whether the "lazy" mode is enabled.
* @param $when
*/
protected function xautoloadCheckTestEnvironment($observations, $cache_types, $lazy, $when) {
// Check early-bootstrap variables.
$label = "$when: xautoload_cache_types:";
$this->assertEqualBlock($cache_types, $observations[XAUTOLOAD_VARNAME_CACHE_TYPES], $label);
$label = "$when: xautoload_cache_lazy:";
$this->assertEqualInline($lazy, $observations[XAUTOLOAD_VARNAME_CACHE_LAZY], $label);
// Check registered class loaders.
$expected = $this->expectedAutoloadStackOrder($cache_types);
$actual = $observations['spl_autoload_functions'];
$label = "$when: spl autoload stack:";
$this->assertEqualBlock($expected, $actual, $label);
}
/**
* @param string $cache_types
* The autoloader modes that are enabled, e.g.
* array('apc' => 'apc', 'xcache' => 'xcache')
*
* @return string[]
* Expected order of class loaders on the spl autoload stack for the given
* autoloader mode. Each represented by a string.
*/
protected function expectedAutoloadStackOrder($cache_types) {
if (!empty($cache_types['apc']) && extension_loaded('apc') && function_exists('apc_store')) {
$loader = 'Drupal\xautoload\ClassLoader\ApcClassLoader->loadClass()';
}
elseif (!empty($cache_types['wincache']) && extension_loaded('wincache') && function_exists('wincache_ucache_get')) {
$loader = 'Drupal\xautoload\ClassLoader\WinCacheClassLoader->loadClass()';
}
elseif (!empty($cache_types['xcache']) && extension_loaded('Xcache') && function_exists('xcache_get')) {
$loader = 'Drupal\xautoload\ClassLoader\XCacheClassLoader->loadClass()';
}
else {
$loader = 'Drupal\xautoload\ClassFinder\ClassFinder->loadClass()';
}
return array(
'drupal_autoload_class',
'drupal_autoload_interface',
$loader,
);
}
/**
* Assert that a module is disabled.
*
* @param string $module
*/
protected function assertModuleDisabled($module) {
$this->assertFalse(module_exists($module), "Module $module is disabled.");
}
/**
* Assert that a module is enabled.
*
* @param string $module
*/
protected function assertModuleEnabled($module) {
$this->assertTrue(module_exists($module), "Module $module is enabled.");
}
/**
* Assert that a class is defined.
*
* @param string $class
*/
protected function assertClassExists($class) {
$this->assertTrue(class_exists($class), "Class '$class' must exist.");
}
/**
* @param mixed $expected
* @param mixed $actual
* @param string $label
*/
protected function assertEqualBlock($expected, $actual, $label) {
$label .=
'Expected: <pre>' . var_export($expected, TRUE) . '</pre>' .
'Actual: <pre>' . var_export($actual, TRUE) . '</pre>';
$this->assertEqual($expected, $actual, $label);
}
/**
* @param mixed $expected
* @param mixed $actual
* @param string $label
*/
protected function assertEqualInline($expected, $actual, $label) {
$label .= '<br/>' .
'Expected: <code>' . var_export($expected, TRUE) . '</code><br/>' .
'Actual: <code>' . var_export($actual, TRUE) . '</code>';
$this->assertEqual($expected, $actual, $label);
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="XAutoload Test Suite">
<directory>./tests/src</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,287 @@
<?php
namespace Drupal\xautoload\Adapter;
use Drupal\xautoload\Discovery\ClassMapGenerator;
use Drupal\xautoload\Util;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use Drupal\xautoload\Discovery\ComposerDir;
use Drupal\xautoload\Discovery\ComposerJson;
use Drupal\xautoload\ClassFinder\GenericPrefixMap;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\Discovery\ClassMapGeneratorInterface;
/**
* An instance of this class is passed around to implementations of
* hook_xautoload(). It acts as a wrapper around the ClassFinder, to register
* stuff.
*/
class ClassFinderAdapter implements ClassFinderAdapterInterface {
/**
* @var ExtendedClassFinderInterface
*/
protected $finder;
/**
* @var GenericPrefixMap
*/
protected $prefixMap;
/**
* @var GenericPrefixMap
*/
protected $namespaceMap;
/**
* @var ClassMapGeneratorInterface
*/
protected $classMapGenerator;
/**
* @param ExtendedClassFinderInterface $finder
*
* @return self
*/
static function create($finder) {
return new self($finder, new ClassMapGenerator());
}
/**
* @param ExtendedClassFinderInterface $finder
* The class finder object.
* @param ClassMapGeneratorInterface $classmap_generator
*/
function __construct($finder, $classmap_generator) {
$this->finder = $finder;
$this->prefixMap = $finder->getPrefixMap();
$this->namespaceMap = $finder->getNamespaceMap();
$this->defaultBehavior = new DefaultDirectoryBehavior();
$this->psr0Behavior = new Psr0DirectoryBehavior();
$this->classMapGenerator = $classmap_generator;
}
/**
* @return \Drupal\xautoload\ClassFinder\GenericPrefixMap
*/
function getNamespaceMap() {
return $this->namespaceMap;
}
/**
* @return GenericPrefixMap
*/
function getPrefixMap() {
return $this->prefixMap;
}
/**
* @return ClassMapGeneratorInterface
*/
function getClassmapGenerator() {
return $this->classMapGenerator;
}
/**
* @return ClassMapGeneratorInterface
*/
function getFinder() {
return $this->finder;
}
// Discovery
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addClassmapSources($paths) {
$map = $this->classMapGenerator->wildcardPathsToClassmap($paths);
$this->addClassMap($map);
}
// Composer tools
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function composerJson($file) {
$json = ComposerJson::createFromFile($file);
$json->writeToAdapter($this);
}
/**
* {@inheritdoc}
*/
function composerDir($dir) {
$dir = ComposerDir::create($dir);
$dir->writeToAdapter($this);
}
// multiple PSR-0 / PSR-4
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addMultiplePsr0(array $prefixes) {
$namespace_map = array();
$prefix_map = array();
foreach ($prefixes as $prefix => $paths) {
if (FALSE === strpos($prefix, '\\')) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$prefix_map[$logical_base_path][$deep_path] = $this->defaultBehavior;
}
}
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$namespace_map[$logical_base_path][$deep_path] = $this->psr0Behavior;
}
}
if (!empty($prefix_map)) {
$this->prefixMap->registerDeepPaths($prefix_map);
}
$this->namespaceMap->registerDeepPaths($namespace_map);
}
/**
* {@inheritdoc}
*/
function addMultiplePsr4(array $map) {
$namespace_map = array();
foreach ($map as $namespace => $paths) {
$logical_base_path = Util::namespaceLogicalPath($namespace);
foreach ($paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/'
: '';
$namespace_map[$logical_base_path][$deep_path] = $this->defaultBehavior;
}
}
$this->namespaceMap->registerDeepPaths($namespace_map);
}
// Composer ClassLoader
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addClassMap(array $classMap) {
$this->finder->registerClasses($classMap);
}
/**
* {@inheritdoc}
*/
function add($prefix, $paths) {
if (FALSE === strpos($prefix, '\\')) {
// Due to the ambiguity of PSR-0, this could be either PEAR-like or namespaced.
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
// Namespaced PSR-0
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->psr0Behavior);
}
}
/**
* {@inheritdoc}
*/
function addPsr0($prefix, $paths) {
$this->add($prefix, $paths);
}
/**
* {@inheritdoc}
*/
function addPsr4($prefix, $paths) {
// Namespaced PSR-4
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $deep_path) {
$deep_path = strlen($deep_path)
? rtrim($deep_path, '/') . '/'
: '';
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
// More convenience stuff
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addNamespacePsr0($prefix, $paths) {
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->psr0Behavior);
}
}
/**
* {@inheritdoc}
*/
function addPear($prefix, $paths) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
/**
* {@inheritdoc}
*/
function addPearFlat($prefix, $paths) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $deep_path) {
$deep_path = strlen($deep_path) ? (rtrim($deep_path, '/') . '/') : '';
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior
);
}
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Drupal\xautoload\Adapter;
use Drupal\xautoload\ClassFinder\CommonRegistrationInterface;
interface ClassFinderAdapterInterface extends CommonRegistrationInterface {
// Discovery
// ---------------------------------------------------------------------------
/**
* @param string[] $paths
* File paths or wildcard paths for class discovery.
*/
function addClassmapSources($paths);
// Composer tools
// ---------------------------------------------------------------------------
/**
* Scan a composer.json file provided by a Composer package.
*
* @param string $file
*
* @throws \Exception
*/
function composerJson($file);
/**
* Scan a directory containing Composer-generated autoload files.
*
* @param string $dir
* Directory to look for Composer-generated files. Typically this is the
* ../vendor/composer dir.
*/
function composerDir($dir);
// multiple PSR-0 / PSR-4
// ---------------------------------------------------------------------------
/**
* Add multiple PSR-0 namespaces
*
* @param array $prefixes
*/
function addMultiplePsr0(array $prefixes);
/**
* Add multiple PSR-4 namespaces
*
* @param array $map
*/
function addMultiplePsr4(array $map);
}

View File

@ -0,0 +1,174 @@
<?php
namespace Drupal\xautoload\Adapter;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\ClassFinder\Plugin\DrupalExtensionNamespaceFinderPlugin;
use Drupal\xautoload\ClassFinder\Plugin\DrupalExtensionUnderscoreFinderPlugin;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
/**
* Service that knows how to register module namespaces and prefixes into the
* class loader, and that remembers which modules have already been registered.
*/
class DrupalExtensionAdapter {
/**
* @var \Drupal\xautoload\DrupalSystem\DrupalSystemInterface
*/
protected $system;
/**
* @var ExtendedClassFinderInterface
*/
protected $finder;
/**
* One finder plugin for each extension type ('module', 'theme').
*
* @var DrupalExtensionNamespaceFinderPlugin[]
*/
protected $namespaceBehaviors = array();
/**
* One finder plugin for each extension type ('module', 'theme').
*
* @var DrupalExtensionUnderscoreFinderPlugin[]
*/
protected $prefixBehaviors = array();
/**
* The namespace map used in the class loader.
*
* @var \Drupal\xautoload\ClassFinder\GenericPrefixMap
*/
protected $namespaceMap;
/**
* The prefix map used in the class loader.
*
* @var \Drupal\xautoload\ClassFinder\GenericPrefixMap
*/
protected $prefixMap;
/**
* Which modules have already been processed.
*
* @var bool[]
*/
protected $registered = array();
/**
* Directory behavior for PSR-4
*
* @var DefaultDirectoryBehavior
*/
protected $defaultBehavior;
/**
* @param DrupalSystemInterface $system
* @param ExtendedClassFinderInterface $finder
*/
function __construct(DrupalSystemInterface $system, ExtendedClassFinderInterface $finder) {
$this->system = $system;
$this->finder = $finder;
$this->namespaceMap = $finder->getNamespaceMap();
$this->prefixMap = $finder->getPrefixMap();
foreach (array('module', 'theme') as $extension_type) {
$this->namespaceBehaviors[$extension_type] = new DrupalExtensionNamespaceFinderPlugin(
$extension_type,
$this->namespaceMap,
$this->prefixMap,
$this->system);
$this->prefixBehaviors[$extension_type] = new DrupalExtensionUnderscoreFinderPlugin(
$extension_type,
$this->namespaceMap,
$this->prefixMap,
$this->system);
}
$this->defaultBehavior = new DefaultDirectoryBehavior();
}
/**
* Register lazy plugins for enabled Drupal modules and themes, assuming that
* we don't know yet whether they use PSR-0, PEAR-Flat, or none of these.
*
* @param string[] $extensions
* An array where the keys are extension names, and the values are extension
* types like 'module' or 'theme'.
*/
function registerExtensions(array $extensions) {
$prefix_map = array();
$namespace_map = array();
foreach ($extensions as $name => $type) {
if (empty($this->namespaceBehaviors[$type])) {
// Unsupported extension type, e.g. "theme_engine".
// This can happen if a site was upgraded from Drupal 6.
// See https://drupal.org/comment/8503979#comment-8503979
continue;
}
if (!empty($this->registered[$name])) {
// The extension has already been processed.
continue;
}
$namespace_map['Drupal/' . $name . '/'][$name] = $this->namespaceBehaviors[$type];
$prefix_map[str_replace('_', '/', $name) . '/'][$name] = $this->prefixBehaviors[$type];
$this->registered[$name] = TRUE;
}
$this->namespaceMap->registerDeepPaths($namespace_map);
$this->prefixMap->registerDeepPaths($prefix_map);
}
/**
* Register lazy plugins for a given extension, assuming that we don't know
* yet whether it uses PSR-0, PEAR-Flat, or none of these.
*
* @param string $name
* @param string $type
*/
function registerExtension($name, $type) {
if (!empty($this->registered[$name])) {
// The extension has already been processed.
return;
}
$this->namespaceMap->registerDeepPath('Drupal/' . $name . '/', $name, $this->namespaceBehaviors[$type]);
$this->prefixMap->registerDeepPath(str_replace('_', '/', $name) . '/', $name, $this->prefixBehaviors[$type]);
$this->registered[$name] = TRUE;
}
/**
* Register PSR-4 directory for an extension.
* Override previous settings for this extension.
*
* @param string $name
* The extension name.
* @param string $extension_dir
* The directory of the extension.
* @param string $subdir
* The PSR-4 base directory, relative to the extension directory.
* E.g. 'lib' or 'src'.
*/
function registerExtensionPsr4($name, $extension_dir, $subdir) {
if (!empty($this->registered[$name])) {
if ('psr-4' === $this->registered[$name]) {
// It already happened.
return;
}
// Unregister the lazy plugins.
$this->namespaceMap->unregisterDeepPath('Drupal/' . $name . '/', $name);
$this->prefixMap->unregisterDeepPath(str_replace('_', '/', $name) . '/', $name);
}
$dir = strlen($subdir)
? $extension_dir . '/' . trim($subdir, '/') . '/'
: $extension_dir . '/';
$this->namespaceMap->registerDeepPath('Drupal/' . $name . '/', $dir, $this->defaultBehavior);
// Re-add the PSR-0 test directory, for consistency's sake.
if (is_dir($psr0_tests_dir = $extension_dir . '/lib/Drupal/' . $name . '/Tests')) {
$this->namespaceMap->registerDeepPath('Drupal/' . $name . '/Tests/', $psr0_tests_dir, $this->defaultBehavior);
}
}
}

View File

@ -0,0 +1,278 @@
<?php
namespace Drupal\xautoload\Adapter;
use Drupal\xautoload\Discovery\ComposerDir;
use Drupal\xautoload\Discovery\ComposerJson;
/**
* An instance of this class is passed around to implementations of
* hook_xautoload(). It acts as a wrapper around the ClassFinder, to register
* stuff.
*/
class LocalDirectoryAdapter implements ClassFinderAdapterInterface {
/**
* @var string
*/
protected $localDirectory;
/**
* @var ClassFinderAdapter
*/
protected $master;
/**
* @param ClassFinderAdapter $adapter
* The class finder object.
* @param string $localDirectory
*/
function __construct(ClassFinderAdapter $adapter, $localDirectory) {
// parent::__construct($adapter->finder, $adapter->getClassmapGenerator());
$this->master = $adapter;
$this->localDirectory = strlen($localDirectory)
? rtrim($localDirectory, '/') . '/'
: '';
}
/**
* Returns an adapter object that is not relative to a local directory.
*
* @return ClassFinderAdapter
*/
function absolute() {
return $this->master;
}
// Discovery
// ---------------------------------------------------------------------------
/**
* Adds source paths for classmap discovery.
*
* The classmap for each source will be cached between requests.
* A "clear all caches" will trigger a rescan.
*
* @param string[] $paths
* File paths or wildcard paths for class discovery.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addClassmapSources($paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->addClassmapSources($paths);
}
// Composer tools
// ---------------------------------------------------------------------------
/**
* Scans a composer.json file provided by a Composer package.
*
* @param string $file
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*
* @throws \Exception
*/
function composerJson($file, $relative = TRUE) {
$relative && $file = $this->localDirectory . $file;
$json = ComposerJson::createFromFile($file);
$json->writeToAdapter($this->master);
}
/**
* Scans a directory containing Composer-generated autoload files.
*
* @param string $dir
* Directory to look for Composer-generated files. Typically this is the
* ../vendor/composer dir.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function composerDir($dir, $relative = TRUE) {
$relative && $dir = $this->localDirectory . $dir;
$dir = ComposerDir::create($dir);
$dir->writeToAdapter($this->master);
}
// multiple PSR-0 / PSR-4
// ---------------------------------------------------------------------------
/**
* Adds multiple PSR-0 prefixes.
*
* @param array $prefixes
* Each array key is a PSR-0 prefix, e.g. "Acme\\FooPackage\\".
* Each array value is either a PSR-0 base directory or an array of PSR-0
* base directories.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addMultiplePsr0(array $prefixes, $relative = TRUE) {
$relative && $this->prependMultiple($prefixes);
$this->master->addMultiplePsr0($prefixes);
}
/**
* Adds multiple PSR-4 namespaces.
*
* @param array $map
* Each array key is a namespace, e.g. "Acme\\FooPackage\\".
* Each array value is either a PSR-4 base directory or an array of PSR-4
* base directories.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addMultiplePsr4(array $map, $relative = TRUE) {
$relative && $this->prependMultiple($map);
$this->master->addMultiplePsr4($map);
}
// Composer ClassLoader
// ---------------------------------------------------------------------------
/**
* Registers an array ("map") of classes to file paths.
*
* @param array $classMap
* The map of classes to file paths.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addClassMap(array $classMap, $relative = TRUE) {
$relative && $this->prependToPaths($classMap);
$this->master->addClassMap($classMap);
}
/**
* Adds a PSR-0 style prefix. Alias for ->addPsr0().
*
* @param string $prefix
* @param string|\string[] $paths
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function add($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->add($prefix, $paths);
}
/**
* Adds a PSR-0 style prefix. Alias for ->add().
*
* @param string $prefix
* @param string|\string[] $paths
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addPsr0($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->add($prefix, $paths);
}
/**
* Adds a PSR-4 style namespace.
*
* @param string $prefix
* @param string|\string[] $paths
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addPsr4($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->addPsr4($prefix, $paths);
}
// More convenience stuff
// ---------------------------------------------------------------------------
/**
* Adds a PSR-0 style namespace.
*
* This will assume that we are really dealing with a namespace, even if it
* has no '\\' included.
*
* @param string $prefix
* @param string|\string[] $paths
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addNamespacePsr0($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->addNamespacePsr0($prefix, $paths);
}
/**
* Adds a PEAR-like prefix.
*
* This will assume with no further checks that $prefix contains no namespace
* separator.
*
* @param string $prefix
* The prefix, e.g. 'Acme_FooPackage_'
* @param string|string[] $paths
* An array of paths, or one specific path.
* E.g. 'lib' for $relative = TRUE,
* or 'sites/all/libraries/AcmeFooPackage/lib' for $relative = FALSE.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addPear($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->addPear($prefix, $paths);
}
/**
* Adds a prefix similar to PEAR, but with flat directories.
*
* This will assume with no further checks that $prefix contains no namespace
* separator.
*
* @param string $prefix
* The prefix, e.g. 'Acme_FooPackage_'
* @param string|string[] $paths
* An array of paths, or one specific path.
* E.g. 'lib' for $relative = TRUE,
* or 'sites/all/libraries/AcmeFooPackage/lib' for $relative = FALSE.
* @param bool $relative
* If TRUE, the paths will be relative to $this->localDirectory.
*/
function addPearFlat($prefix, $paths, $relative = TRUE) {
$relative && $this->prependToPaths($paths);
$this->master->addPearFlat($prefix, $paths);
}
// Relative path handling
// ---------------------------------------------------------------------------
/**
* Prepends $this->localDirectory to a number of paths.
*
* @param array $map
*/
protected function prependMultiple(array &$map) {
foreach ($map as &$paths) {
$paths = (array) $paths;
foreach ($paths as &$path) {
$path = $this->localDirectory . $path;
}
}
}
/**
* Prepends $this->localDirectory to a number of paths.
*
* @param string|string[] &$paths
*/
protected function prependToPaths(&$paths) {
if (!is_array($paths)) {
$paths = $this->localDirectory . $paths;
}
else {
foreach ($paths as &$path) {
$path = $this->localDirectory . $path;
}
}
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Drupal\xautoload\CacheManager;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\Util;
class CacheManager {
/**
* @var string
*/
protected $prefix;
/**
* @var \Drupal\xautoload\DrupalSystem\DrupalSystemInterface
*/
protected $system;
/**
* @var CacheManagerObserverInterface[]
*/
protected $observers = array();
/**
* @param string $prefix
* @param \Drupal\xautoload\DrupalSystem\DrupalSystemInterface $system
*/
protected function __construct($prefix, DrupalSystemInterface $system) {
$this->prefix = $prefix;
$this->system = $system;
}
/**
* This method has side effects, so it is not the constructor.
*
* @param \Drupal\xautoload\DrupalSystem\DrupalSystemInterface $system
*
* @return CacheManager
*/
static function create(DrupalSystemInterface $system) {
$prefix = $system->variableGet(XAUTOLOAD_VARNAME_CACHE_PREFIX, NULL);
$manager = new self($prefix, $system);
if (empty($prefix)) {
$manager->renewCachePrefix();
}
return $manager;
}
/**
* @param CacheManagerObserverInterface $observer
*/
function observeCachePrefix($observer) {
$observer->setCachePrefix($this->prefix);
$this->observers[] = $observer;
}
/**
* Renew the cache prefix, save it, and notify all observers.
*/
function renewCachePrefix() {
$this->prefix = Util::randomString();
$this->system->variableSet(XAUTOLOAD_VARNAME_CACHE_PREFIX, $this->prefix);
foreach ($this->observers as $observer) {
$observer->setCachePrefix($this->prefix);
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Drupal\xautoload\CacheManager;
interface CacheManagerObserverInterface {
/**
* Set the APC prefix after a flush cache.
*
* @param string $prefix
* A prefix for the storage key in APC.
*/
function setCachePrefix($prefix);
}

View File

@ -0,0 +1,48 @@
<?php
namespace Drupal\xautoload\CacheMissObserver;
use Drupal\xautoload\ClassLoader\AbstractClassLoaderDecorator;
/**
* Replaces the ProxyClassFinder in the ClassLoader with the real ClassLoader.
*
* xautoload has a number of cached class loaders, working with APC cache or
* other key-value stores.
*
* The cached class loaders use the decorator pattern, and decorate a
* ClassFinder object, that will only be consulted on a cache miss.
*
* xautoload will first give the ClassLoader a ProxyClassFinder that wraps the
* real class loader. On the first cache miss, this ProxyClassFinder will
* notify all subscribed CacheMissObserverInterface object.
*
* The job of this particular observer is to replace the ProxyClassFinder, once
* it has done its job. Instead, the ClassLoader will get a reference ot the
* real ClassLoader, saving the overhead of going through ProxyClassFinder each
* time.
*
* @see ProxyClassFinder
*/
class CacheMissLoaderSetFinder implements CacheMissObserverInterface {
/**
* @var AbstractClassLoaderDecorator
*/
protected $loader;
/**
* @param AbstractClassLoaderDecorator $loader
*/
function __construct($loader) {
$this->loader = $loader;
}
/**
* {@inheritdoc}
*/
function cacheMiss($finder) {
$this->loader->setFinder($finder);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Drupal\xautoload\CacheMissObserver;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
/**
* An operation that can be queued up to be performed on the class finder once
* it is initialized.
*
* In an average request, with the APC cache or similar enabled, class-to-file
* mappings will usually be loaded from the cache. The "real" class finder will
* only be initialized if one of the classes is not in the cache.
*
* @see ProxyClassFinder
*/
interface CacheMissObserverInterface {
/**
* Executes the operation.
*
* This method will only be called if and when the "real" class finder is
* initialized.
*
* @param ExtendedClassFinderInterface $finder
* The class finder.
*/
function cacheMiss($finder);
}

View File

@ -0,0 +1,468 @@
<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\ClassLoader\AbstractClassLoader;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
use Drupal\xautoload\Util;
class ClassFinder extends AbstractClassLoader implements ExtendedClassFinderInterface {
/**
* @var array[]
*/
protected $classes = array();
/**
* @var GenericPrefixMap
*/
protected $prefixMap;
/**
* @var GenericPrefixMap
*/
protected $namespaceMap;
/**
* @var DefaultDirectoryBehavior
*/
protected $defaultBehavior;
/**
* @var Psr0DirectoryBehavior
*/
protected $psr0Behavior;
function __construct() {
$this->prefixMap = new GenericPrefixMap('_');
$this->namespaceMap = new GenericPrefixMap('\\');
$this->defaultBehavior = new DefaultDirectoryBehavior();
$this->psr0Behavior = new Psr0DirectoryBehavior();
}
/**
* {@inheritdoc}
*/
function getPrefixMap() {
return $this->prefixMap;
}
/**
* {@inheritdoc}
*/
function getNamespaceMap() {
return $this->namespaceMap;
}
// Composer compatibility
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addClassMap(array $classMap) {
$this->registerClasses($classMap);
}
/**
* {@inheritdoc}
*/
function add($prefix, $paths) {
if (FALSE === strpos($prefix, '\\')) {
// Due to the ambiguity of PSR-0, this could be either PEAR-like or namespaced.
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
// Namespaced PSR-0
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->psr0Behavior);
}
}
/**
* {@inheritdoc}
*/
function addPsr0($prefix, $paths) {
$this->add($prefix, $paths);
}
/**
* {@inheritdoc}
*/
function addPsr4($prefix, $paths) {
// Namespaced PSR-4
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $deep_path) {
$deep_path = strlen($deep_path)
? rtrim($deep_path, '/') . '/'
: '';
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
// More convenience stuff
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function addNamespacePsr0($prefix, $paths) {
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->psr0Behavior);
}
}
/**
* {@inheritdoc}
*/
function addPear($prefix, $paths) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior);
}
}
/**
* {@inheritdoc}
*/
function addPearFlat($prefix, $paths) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $deep_path) {
$deep_path = strlen($deep_path)
? (rtrim($deep_path, '/') . '/')
: '';
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$this->defaultBehavior
);
}
}
// Class map stuff
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function registerClass($class, $file_path) {
$this->classes[$class][$file_path] = TRUE;
}
/**
* {@inheritdoc}
*/
function registerClasses($classes) {
foreach ($classes as $class => $file_path) {
$this->classes[$class][$file_path] = TRUE;
}
}
// Prefix stuff
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function registerPrefixRoot($prefix, $root_path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$logical_base_path = Util::prefixLogicalPath($prefix);
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$behavior);
if (strlen($prefix)) {
// We assume that the class named $prefix is also found at this path.
$filepath = substr($deep_path, 0, -1) . '.php';
$this->registerClass($prefix, $filepath);
}
}
/**
* {@inheritdoc}
*/
function registerPrefixesRoot($map, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$deep_map = array();
foreach ($map as $prefix => $root_path) {
$logical_base_path = Util::prefixLogicalPath($prefix);
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$deep_map[$logical_base_path][$deep_path] = $behavior;
// Register the class with name $prefix.
if (strlen($prefix)) {
$filepath = substr($deep_path, 0, -1) . '.php';
$this->classes[$prefix][$filepath] = TRUE;
}
}
$this->prefixMap->registerDeepPaths($deep_map);
}
/**
* {@inheritdoc}
*/
function registerPrefixDeep($prefix, $deep_path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$this->registerPrefixDeepLocation($prefix, $deep_path, $behavior);
}
/**
* {@inheritdoc}
*/
function registerPrefixesDeep($map, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$deep_map = array();
foreach ($map as $prefix => $deep_path) {
$logical_base_path = Util::prefixLogicalPath($prefix);
$deep_path = strlen($deep_path)
? rtrim($deep_path, '/') . '/'
: '';
$deep_map[$logical_base_path][$deep_path] = $behavior;
}
$this->prefixMap->registerDeepPaths($deep_map);
}
/**
* {@inheritdoc}
*/
function registerPrefixDeepLocation($prefix, $deep_path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$logical_base_path = Util::prefixLogicalPath($prefix);
$deep_path = strlen($deep_path)
? rtrim($deep_path, '/') . '/'
: '';
$this->prefixMap->registerDeepPath(
$logical_base_path,
$deep_path,
$behavior);
}
// Namespace stuff
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function registerNamespaceRoot($namespace, $root_path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$logical_base_path = Util::namespaceLogicalPath($namespace);
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$behavior);
}
/**
* {@inheritdoc}
*/
function registerNamespacesRoot($map, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$deep_map = array();
foreach ($map as $namespace => $root_path) {
$logical_base_path = Util::namespaceLogicalPath($namespace);
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
$deep_map[$logical_base_path][$deep_path] = $behavior;
}
$this->namespaceMap->registerDeepPaths($deep_map);
}
/**
* {@inheritdoc}
*/
function registerNamespaceDeep($namespace, $path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$logical_base_path = Util::namespaceLogicalPath($namespace);
$deep_path = strlen($path)
? $path . '/'
: '';
$this->namespaceMap->registerDeepPath(
$logical_base_path,
$deep_path,
$behavior);
}
/**
* {@inheritdoc}
*/
function registerNamespacesDeep($map, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$deep_map = array();
foreach ($map as $namespace => $deep_path) {
$logical_base_path = Util::namespaceLogicalPath($namespace);
$deep_path = strlen($deep_path)
? rtrim($deep_path, '/') . '/'
: '';
$deep_map[$logical_base_path][$deep_path] = $behavior;
}
$this->namespaceMap->registerDeepPaths($deep_map);
}
/**
* {@inheritdoc}
*/
function registerNamespaceDeepLocation($namespace, $path, $behavior = NULL) {
if (!isset($behavior)) {
$behavior = $this->defaultBehavior;
}
$namespace_path_fragment = Util::namespaceLogicalPath($namespace);
$deep_path = strlen($path)
? $path . '/'
: '';
$this->namespaceMap->registerDeepPath(
$namespace_path_fragment,
$deep_path,
$behavior);
}
// ---------------------------------------------------------------------------
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Fix the behavior of some PHP versions that prepend '\\' to the class name.
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
// First check if the literal class name is registered.
if (!empty($this->classes[$class])) {
foreach ($this->classes[$class] as $filepath => $true) {
if (file_exists($filepath)) {
require $filepath;
return TRUE;
}
}
}
// Check if the class has a namespace.
if (FALSE !== $pos = strrpos($class, '\\')) {
// Build the "logical path" based on PSR-4 replacement rules.
$logical_path = str_replace('\\', '/', $class) . '.php';
return $this->namespaceMap->loadClass($class, $logical_path, $pos);
}
// Build the "logical path" based on PEAR replacement rules.
$pear_logical_path = str_replace('_', '/', $class) . '.php';
// Clean up surplus '/' resulting from duplicate underscores, or an
// underscore at the beginning of the class.
while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
$pear_logical_path[$pos] = '_';
}
// Check if the class has any underscore.
$pos = strrpos($pear_logical_path, '/');
return $this->prefixMap->loadClass($class, $pear_logical_path, $pos);
}
/**
* {@inheritdoc}
*/
function apiFindFile($api, $class) {
// Fix the behavior of some PHP versions that prepend '\\' to the class name.
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
// First check if the literal class name is registered.
if (!empty($this->classes[$class])) {
foreach ($this->classes[$class] as $filepath => $true) {
if ($api->suggestFile($filepath)) {
return TRUE;
}
}
}
// Check if the class has a namespace.
if (FALSE !== $pos = strrpos($class, '\\')) {
// Build the "logical path" based on PSR-4 replacement rules.
$logical_path = str_replace('\\', '/', $class) . '.php';
return $this->namespaceMap->apiFindFile($api, $logical_path, $pos);
}
// Build the "logical path" based on PEAR replacement rules.
$pear_logical_path = str_replace('_', '/', $class) . '.php';
// Clean up surplus '/' resulting from duplicate underscores, or an
// underscore at the beginning of the class.
while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
$pear_logical_path[$pos] = '_';
}
// Check if the class has any underscore.
$pos = strrpos($pear_logical_path, '/');
return $this->prefixMap->apiFindFile($api, $pear_logical_path, $pos);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\ClassLoader\ClassLoaderInterface;
interface ClassFinderInterface extends ClassLoaderInterface {
/**
* Finds the path to the file where the class is defined.
*
* @param \Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface $api
* API object with a suggestFile() method.
* We are supposed to call $api->suggestFile($file) with all suggestions we
* can find, until it returns TRUE. Once suggestFile() returns TRUE, we stop
* and return TRUE as well. The $file will be in the $api object, so we
* don't need to return it.
* @param string $class
* The name of the class, with all namespaces prepended.
* E.g. Some\Namespace\Some\Class
*
* @return TRUE|NULL
* TRUE, if we found the file for the class.
* That is, if the $api->suggestFile($file) method returned TRUE one time.
* NULL, if we have no more suggestions.
*/
function apiFindFile($api, $class);
}

View File

@ -0,0 +1,81 @@
<?php
namespace Drupal\xautoload\ClassFinder;
/**
* Class finder interface with additional registration methods.
*/
interface CommonRegistrationInterface {
// Composer compatibility
// ---------------------------------------------------------------------------
/**
* Registers an array ("map") of classes to file paths.
*
* @param array $classMap
* Class to filename map. E.g. $classMap['Acme\Foo'] = 'lib/Acme/Foo.php'
*/
function addClassMap(array $classMap);
/**
* Adds a PSR-0 style prefix. Alias for ->addPsr0().
*
* @param string $prefix
* @param string[]|string $paths
*/
function add($prefix, $paths);
/**
* Adds a PSR-0 style prefix. Alias for ->add().
*
* @param string $prefix
* @param string[]|string $paths
*/
function addPsr0($prefix, $paths);
/**
* Adds a PSR-4 style namespace.
*
* @param string $prefix
* @param string[]|string $paths
*/
function addPsr4($prefix, $paths);
// More convenience stuff
// ---------------------------------------------------------------------------
/**
* Adds a PSR-0 style namespace.
*
* This will assume that we are really dealing with a namespace, even if it
* has no '\\' included.
*
* @param string $prefix
* @param string[]|string $paths
*/
function addNamespacePsr0($prefix, $paths);
/**
* Adds a PEAR-like prefix.
*
* This will assume with no further checks that $prefix contains no namespace
* separator.
*
* @param string $prefix
* @param string[]|string $paths
*/
function addPear($prefix, $paths);
/**
* Adds a prefix similar to PEAR, but with flat directories.
*
* This will assume with no further checks that $prefix contains no namespace
* separator.
*
* @param string $prefix
* @param string[]|string $paths
*/
function addPearFlat($prefix, $paths);
}

View File

@ -0,0 +1,215 @@
<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface;
/**
* Class finder interface with additional registration methods.
*/
interface ExtendedClassFinderInterface extends ClassFinderInterface, CommonRegistrationInterface {
/**
* @return GenericPrefixMap
*/
function getPrefixMap();
/**
* @return GenericPrefixMap
*/
function getNamespaceMap();
// Class map stuff
// ---------------------------------------------------------------------------
/**
* Register a filepath for an individual class.
*
* @param string $class
* The class, e.g. My_Class
* @param string $file_path
* The path, e.g. "../lib/My/Class.php".
*/
function registerClass($class, $file_path);
/**
* Register an array ("map") of classes to file paths.
*
* @param string[] $classes
* The map of classes to file paths.
*/
function registerClasses($classes);
// Prefix stuff
// ---------------------------------------------------------------------------
/**
* Register a PEAR-style root path for a given class prefix.
*
* @param string $prefix
* Prefix, e.g. "My_Prefix", for classes like "My_Prefix_SomeClass".
* This does ALSO cover the class named "My_Prefix" itself.
* @param string $root_path
* Root path, e.g. "../lib" or "../src", so that classes can be placed e.g.
* My_Prefix_SomeClass -> ../lib/My/Prefix/SomeClass.php
* My_Prefix -> ../lib/My/Prefix.php
* @param DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerPrefixRoot($prefix, $root_path, $behavior = NULL);
/**
* Register an array of PEAR-style deep paths for given class prefixes.
*
* Note:
* This actually goes beyond PEAR style, because it also allows "shallow"
* PEAR-like structures like
* my_library_Some_Class -> (library dir)/src/Some/Class.php
* instead of
* my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
* via
* $finder->registerPrefixDeep('my_library', "$library_dir/src");
*
* @param string[] $map
* Associative array, the keys are the prefixes, the values are the
* directories.
* This does NOT cover the class named $prefix itself.
* @param DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerPrefixesRoot($map, $behavior = NULL);
/**
* Register a PEAR-style deep path for a given class prefix.
*
* Note:
* This actually goes beyond PEAR style, because it also allows things like
* my_library_Some_Class -> (library dir)/src/Some/Class.php
* instead of
* my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
* via
* $finder->registerPrefixDeep('my_library', "$library_dir/src");
*
* @param string $prefix
* Prefix, e.g. "My_Prefix", for classes like "My_Prefix_SomeClass".
* This does NOT cover the class named "My_Prefix" itself.
* @param string $deep_path
* The deep path, e.g. "../lib/My/Prefix", for classes placed in
* My_Prefix_SomeClass -> ../lib/My/Prefix/SomeClass.php
* @param DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerPrefixDeep($prefix, $deep_path, $behavior = NULL);
/**
* Register an array of PEAR-style deep paths for given class prefixes.
*
* Note:
* This actually goes beyond PEAR style, because it also allows "shallow"
* PEAR-like structures like
* my_library_Some_Class -> (library dir)/src/Some/Class.php
* instead of
* my_library_Some_Class -> (library dir)/src/my/library/Some/Class.php
* via
* $finder->registerPrefixDeep('my_library', "$library_dir/src");
*
* @param string[] $map
* Associative array, the keys are the prefixes, the values are the
* directories.
* This does NOT cover the class named $prefix itself.
* @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerPrefixesDeep($map, $behavior = NULL);
/**
* Register a filesystem location for a given class prefix.
*
* @param string $prefix
* The prefix, e.g. "My_Prefix"
* @param string $deep_path
* The deep filesystem location, e.g. "../lib/My/Prefix".
* @param DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerPrefixDeepLocation($prefix, $deep_path, $behavior = NULL);
// Namespace stuff
// ---------------------------------------------------------------------------
/**
* Register a PSR-0 root folder for a given namespace.
*
* @param string $namespace
* The namespace, e.g. "My\Namespace", to cover all classes within that,
* e.g. My\Namespace\SomeClass, or My\Namespace\Xyz\SomeClass. This does not
* cover the root-level class, e.g. My\Namespace
* @param string $root_path
* The deep path, e.g. "../lib", if classes reside in e.g.
* My\Namespace\SomeClass -> ../lib/My/Namespace/SomeClass.php
* @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerNamespaceRoot($namespace, $root_path, $behavior = NULL);
/**
* Register PSR-0 root folders for given namespaces.
*
* @param string[] $map
* Associative array, the keys are the namespaces, the values are the
* directories.
* @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerNamespacesRoot($map, $behavior = NULL);
/**
* Alias for registerNamespaceDeepLocation()
*
* @param string $namespace
* The namespace, e.g. "My\Namespace"
* @param string $path
* The deep path, e.g. "../lib/My/Namespace"
* @param \Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerNamespaceDeep($namespace, $path, $behavior = NULL);
/**
* Register a number of "deep" namespace directories at once.
*
* @param string[] $map
* @param DirectoryBehaviorInterface $behavior
*/
function registerNamespacesDeep($map, $behavior = NULL);
/**
* Register a deep filesystem location for a given namespace.
*
* @param string $namespace
* The namespace, e.g. "My\Namespace"
* @param string $path
* The deep path, e.g. "../lib/My/Namespace"
* @param DirectoryBehaviorInterface $behavior
* If TRUE, then we are not sure if the directory at $path actually exists.
* If during the process we find the directory to be nonexistent, we
* unregister the path.
*/
function registerNamespaceDeepLocation($namespace, $path, $behavior = NULL);
}

View File

@ -0,0 +1,242 @@
<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\DirectoryBehavior\DirectoryBehaviorInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassInjectedAPI;
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use xautoload_FinderPlugin_Interface;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
/**
* Helper class for the class finder.
* This is not part of ClassFinder, because we want to use the same logic for
* namespaces (PSR-0) and prefixes (PEAR).
*
* This thing does not actually deal with class names, but with transformed
* paths.
*
* Example A:
* When looking for a class \Aaa\Bbb\Ccc_Ddd, the class finder will
* 1. Determine that this class is within a namespace.
* 2. Transform that into "Aaa/Bbb/Ccc/Ddd.php".
* 3. Check if the namespace map evaluator has anything registered for
* 3.1. "Aaa/Bbb/"
* 3.2. "Aaa/"
* 3.3. ""
*
* Example A:
* When looking for a class Aaa_Bbb_Ccc, the class finder will
* 1. Determine that this class is NOT within a namespace.
* 2. Check if a file is explicitly registered for the class itself.
* 3. Transform the class name into "Aaa/Bbb/Ccc.php".
* 4. Check if the prefix map evaluator has anything registered for
* 4.1. "Aaa/Bbb/"
* 4.2. "Aaa/"
* 4.3. ""
*/
class GenericPrefixMap {
/**
* @var DirectoryBehaviorInterface[][]
* Format: $[$logical_base_path][$deep_path] = $behavior
*/
protected $paths = array();
/**
* @var string
* Either '\\' or '_'.
*/
protected $separator;
/**
* @param string $separator
*/
function __construct($separator) {
$this->separator = $separator;
}
/**
* If a class file would be in
* $psr0_root . '/' . $path_fragment . $path_suffix
* then instead, we look in
* $deep_path . $path_suffix
*
* @param string $logical_base_path
* The would-be namespace path relative to PSR-0 root.
* That is, the namespace with '\\' replaced by '/'.
* @param string $deep_path
* The filesystem location of the (PSR-0) subfolder for the given namespace.
* @param DirectoryBehaviorInterface $behavior
* Behavior in this directory.
*/
function registerDeepPath($logical_base_path, $deep_path, $behavior) {
$this->paths[$logical_base_path][$deep_path] = $behavior;
}
/**
* @param string $logical_base_path
* The would-be namespace path relative to PSR-0 root.
* That is, the namespace with '\\' replaced by '/'.
* @param string $deep_path
* The filesystem location of the (PSR-0) subfolder for the given namespace.
* @param DirectoryBehaviorInterface $behavior
* Behavior in this directory.
*/
function prependDeepPath($logical_base_path, $deep_path, $behavior) {
$this->paths[$logical_base_path]
= isset($this->paths[$logical_base_path])
? array($deep_path => $behavior) + $this->paths[$logical_base_path]
: array($deep_path => $behavior);
}
/**
* Register a bunch of those paths ..
*
* @param array[] $map
*
* @throws \Exception
*/
function registerDeepPaths(array $map) {
foreach ($map as $key => $paths) {
if (isset($this->paths[$key])) {
$paths += $this->paths[$key];
}
$this->paths[$key] = $paths;
}
}
/**
* Delete a registered path mapping.
*
* @param string $logical_base_path
* @param string $deep_path
*/
function unregisterDeepPath($logical_base_path, $deep_path) {
unset($this->paths[$logical_base_path][$deep_path]);
}
/**
* @param string $class
* @param string $logical_path
* Class name translated into a logical path, either with PSR-4 or with PEAR
* translation rules.
* @param int|bool $lastpos
* Position of the last directory separator in $logical_path.
* FALSE, if there is no directory separator in $logical_path.
*
* @return bool|NULL
* TRUE, if the class was found.
*/
function loadClass($class, $logical_path, $lastpos) {
$pos = $lastpos;
while (TRUE) {
$logical_base_path = (FALSE === $pos)
? ''
: substr($logical_path, 0, $pos + 1);
if (isset($this->paths[$logical_base_path])) {
foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
if ($behavior instanceof DefaultDirectoryBehavior) {
// PSR-4 and PEAR
if (file_exists($file = $dir . substr($logical_path, $pos + 1))) {
require $file;
return TRUE;
}
}
elseif ($behavior instanceof Psr0DirectoryBehavior) {
// PSR-0
if (file_exists(
$file = $dir
. substr($logical_path, $pos + 1, $lastpos - $pos)
. str_replace('_', '/', substr($logical_path, $lastpos + 1))
)) {
require $file;
return TRUE;
}
}
elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
// Legacy "FinderPlugin".
$api = new LoadClassInjectedAPI($class);
if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
return TRUE;
}
}
}
}
// Continue with parent fragment.
if (FALSE === $pos) {
return NULL;
}
$pos = strrpos($logical_base_path, '/', -2);
}
return NULL;
}
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* @param InjectedApiInterface $api
* @param string $logical_path
* Class name translated into a logical path, either with PSR-4 or with PEAR
* translation rules.
* @param int|bool $lastpos
* Position of the last directory separator in $logical_path.
* FALSE, if there is no directory separator in $logical_path.
*
* @return bool|NULL
* TRUE, if the class was found.
*/
function apiFindFile($api, $logical_path, $lastpos) {
$pos = $lastpos;
while (TRUE) {
$logical_base_path = (FALSE === $pos)
? ''
: substr($logical_path, 0, $pos + 1);
if (isset($this->paths[$logical_base_path])) {
foreach ($this->paths[$logical_base_path] as $dir => $behavior) {
if ($behavior instanceof DefaultDirectoryBehavior) {
// PSR-4 and PEAR
if ($api->suggestFile($dir . substr($logical_path, $pos + 1))) {
return TRUE;
}
}
elseif ($behavior instanceof Psr0DirectoryBehavior) {
// PSR-0
if ($api->suggestFile(
$dir
. substr($logical_path, $pos + 1, $lastpos - $pos)
. str_replace('_', '/', substr($logical_path, $lastpos + 1))
)) {
return TRUE;
}
}
elseif ($behavior instanceof xautoload_FinderPlugin_Interface) {
// Legacy "FinderPlugin".
if ($behavior->findFile($api, $logical_base_path, substr($logical_path, $pos + 1), $dir)) {
return TRUE;
}
}
}
}
// Continue with parent fragment.
if (FALSE === $pos) {
return NULL;
}
$pos = strrpos($logical_base_path, '/', -2);
}
return NULL;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
abstract class AbstractInjectedApi implements InjectedApiInterface {
/**
* @var string
* The class name to look for. Set in the constructor.
*/
protected $className;
/**
* @param $class_name
* Name of the class or interface we are trying to load.
*/
function __construct($class_name) {
$this->className = $class_name;
}
/**
* This is done in the injected api object, so we can easily provide a mock
* implementation.
*/
function is_dir($dir) {
return is_dir($dir);
}
/**
* Get the name of the class we are looking for.
*
* @return string
* The class we are looking for.
*/
function getClass() {
return $this->className;
}
/**
* Dummy method to force autoloading this class (or an ancestor).
*/
static function forceAutoload() {
// Do nothing.
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
class CollectFilesInjectedApi extends AbstractInjectedApi {
/**
* @var string
* The method that, if called with $this->file, will return TRUE.
*/
protected $methodName;
/**
* @var string
* The file where $this->$method($this->file) will return TRUE.
*/
protected $file;
/**
* @var array[]
* All files that were suggested.
*/
protected $suggestions;
/**
* @param string $class_name
* @var string $method
* The method that, if called with $this->file, will return TRUE.
* @param string $file
* The file where $this->$method($this->file) will return TRUE.
*/
function __construct($class_name, $method_name, $file) {
$this->methodName = $method_name;
$this->file = $file;
parent::__construct($class_name);
}
/**
* When the process has finished, use this to return the result.
*
* @return string
* The file that is supposed to declare the class.
*/
function getSuggestions() {
return $this->suggestions;
}
/**
* Suggest a file that, if the file exists,
* has to declare the class we are looking for.
* Only keep the class on success.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return __FUNCTION__ === $this->methodName && $file === $this->file;
}
/**
* Same as suggestFile(), but skip the file_exists(),
* assuming that we already know the file exists.
*
* This could make sense if a plugin already did the file_exists() check.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_skipFileExists($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return TRUE;
}
/**
* Same as suggestFile(), but assume that file_exists() returns TRUE.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_checkNothing($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return TRUE;
}
/**
* Same as suggestFile(), but check the full PHP include path.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile_checkIncludePath($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return __FUNCTION__ == $this->methodName && $file === $this->file;
}
/**
* {@inheritdoc}
*/
function guessFile($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return __FUNCTION__ == $this->methodName && $file === $this->file;
}
/**
* {@inheritdoc}
*/
function guessPath($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return __FUNCTION__ == $this->methodName && $file === $this->file;
}
/**
* {@inheritdoc}
*/
function claimFile($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return TRUE;
}
/**
* {@inheritdoc}
*/
function claimPath($file) {
$this->suggestions[] = array(__FUNCTION__, $file);
return __FUNCTION__ == $this->methodName && $file === $this->file;
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
use Drupal\xautoload\Util;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
class FindFileInjectedApi extends AbstractInjectedApi {
/**
* @var string
* The file that was found.
*/
protected $file;
/**
* When the process has finished, use this to return the result.
*
* @return string
* The file that is supposed to declare the class.
*/
function getFile() {
return $this->file;
}
/**
* Suggest a file that, if the file exists,
* has to declare the class we are looking for.
* Only keep the class on success.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile($file) {
if (file_exists($file)) {
$this->file = $file;
return TRUE;
}
return FALSE;
}
/**
* Same as suggestFile(), but skip the file_exists(),
* assuming that we already know the file exists.
*
* This could make sense if a plugin already did the file_exists() check.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_skipFileExists($file) {
$this->file = $file;
return TRUE;
}
/**
* Same as suggestFile(), but assume that file_exists() returns TRUE.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_checkNothing($file) {
$this->file = $file;
return TRUE;
}
/**
* Same as suggestFile(), but check the full PHP include path.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile_checkIncludePath($file) {
if (FALSE !== $file = Util::findFileInIncludePath($file)) {
$this->file = $file;
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function guessFile($file) {
// The file must be included, or else we can't know if it defines the class.
require_once $file;
if (Util::classIsDefined($this->className)) {
$this->file = $file;
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function guessPath($file) {
if (file_exists($file)) {
// The file must be included, or else we can't know if it defines the class.
require_once $file;
if (Util::classIsDefined($this->className)) {
$this->file = $file;
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function claimFile($file) {
$this->file = $file;
return TRUE;
}
/**
* {@inheritdoc}
*/
function claimPath($file) {
if (file_exists($file)) {
$this->file = $file;
return TRUE;
}
return FALSE;
}
}

View File

@ -0,0 +1,138 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
interface InjectedApiInterface {
/**
* This is done in the injected api object, so we can easily provide a mock
* implementation.
*/
function is_dir($dir);
/**
* Get the name of the class we are looking for.
*
* @return string
* The class we are looking for.
*/
function getClass();
/**
* Suggest a file that, if the file exists,
* has to declare the class we are looking for.
* Only keep the class on success.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile($file);
/**
* Same as suggestFile(), but skip the file_exists(),
* assuming that we already know the file exists.
*
* This could make sense if a plugin already did the file_exists() check.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_skipFileExists($file);
/**
* Same as suggestFile(), but assume that file_exists() returns TRUE.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_checkNothing($file);
/**
* Same as suggestFile(), but check the full PHP include path.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile_checkIncludePath($file);
/**
* Suggest a file that MUST exists, and that MAY declare the class we are
* looking for.
*
* This is useful if a plugin already did the is_file() check by itself.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return boolean|NULL
* TRUE, if we are not interested in further candidates.
* FALSE|NULL, if we are interested in further candidates.
*/
function guessFile($file);
/**
* Suggest a file that MAY exist, and that MAY declare the class we are
* looking for.
*
* This is useful if a plugin already did the is_file() check by itself.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return boolean|NULL
* TRUE, if we are not interested in further candidates.
* FALSE|NULL, if we are interested in further candidates.
*/
function guessPath($file);
/**
* Suggest a file that MUST exist, and if so, MUST declare the class we are
* looking for.
*
* This is useful if a plugin already did the is_file() check by itself.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return boolean|NULL
* TRUE, if we are not interested in further candidates.
* FALSE|NULL, if we are interested in further candidates.
*/
function claimFile($file);
/**
* Suggest a file that MAY exist, and if so, MUST declare the class we are
* looking for.
*
* This is useful if a plugin already did the is_file() check by itself.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return boolean|NULL
* TRUE, if we are not interested in further candidates.
* FALSE|NULL, if we are interested in further candidates.
*/
function claimPath($file);
}

View File

@ -0,0 +1,135 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
use Drupal\xautoload\Util;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
class LoadClassGetFileInjectedApi extends FindFileInjectedApi {
/**
* Suggest a file that, if the file exists,
* has to declare the class we are looking for.
* Only keep the class on success.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile($file) {
if (file_exists($file)) {
$this->file = $file;
require $file;
return TRUE;
}
else {
return FALSE;
}
}
/**
* Same as suggestFile(), but skip the file_exists(),
* assuming that we already know the file exists.
*
* This could make sense if a plugin already did the file_exists() check.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_skipFileExists($file) {
$this->file = $file;
require $file;
return TRUE;
}
/**
* Same as suggestFile(), but assume that file_exists() returns TRUE.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file was found - which is always.
*/
function suggestFile_checkNothing($file) {
$this->file = $file;
require $file;
return TRUE;
}
/**
* Same as suggestFile(), but check the full PHP include path.
*
* @param string $file
* The file that is supposed to declare the class.
*
* @return bool
* TRUE, if the file exists.
* FALSE, otherwise.
*/
function suggestFile_checkIncludePath($file) {
if (FALSE !== $file = Util::findFileInIncludePath($file)) {
$this->file = $file;
require $file;
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function guessFile($file) {
require_once $file;
if (Util::classIsDefined($this->className)) {
$this->file = $file;
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function guessPath($file) {
if (file_exists($file)) {
require_once $file;
if (Util::classIsDefined($this->className)) {
$this->file = $file;
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
function claimFile($file) {
require $file;
$this->file = $file;
return TRUE;
}
/**
* {@inheritdoc}
*/
function claimPath($file) {
if (file_exists($file)) {
require $file;
$this->file = $file;
return TRUE;
}
return FALSE;
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace Drupal\xautoload\ClassFinder\InjectedApi;
use Drupal\xautoload\Util;
/**
* To help testability, we use an injected API instead of just a return value.
* The injected API can be mocked to provide a mocked file_exists(), and to
* monitor all suggested candidates, not just the correct return value.
*/
class LoadClassInjectedAPI extends AbstractInjectedApi {
/**
* {@inheritdoc}
*/
function suggestFile($file) {
if (file_exists($file)) {
require $file;
return TRUE;
}
else {
return FALSE;
}
}
/**
* {@inheritdoc}
*/
function suggestFile_skipFileExists($file) {
require $file;
return TRUE;
}
/**
* {@inheritdoc}
*/
function suggestFile_checkNothing($file) {
require $file;
return TRUE;
}
/**
* {@inheritdoc}
*/
function suggestFile_checkIncludePath($file) {
if (FALSE !== $file = Util::findFileInIncludePath($file)) {
require $file;
return TRUE;
}
else {
return FALSE;
}
}
/**
* {@inheritdoc}
*/
function guessFile($file) {
require_once $file;
return Util::classIsDefined($this->className);
}
/**
* {@inheritdoc}
*/
function guessPath($file) {
if (file_exists($file)) {
require_once $file;
return Util::classIsDefined($this->className);
}
else {
return FALSE;
}
}
/**
* {@inheritdoc}
*/
function claimFile($file) {
require $file;
return TRUE;
}
/**
* {@inheritdoc}
*/
function claimPath($file) {
if (file_exists($file)) {
require $file;
return TRUE;
}
else {
return FALSE;
}
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Drupal\xautoload\ClassFinder\Plugin;
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
/**
* @see _registry_check_code()
*/
class DrupalCoreRegistryPlugin implements FinderPluginInterface {
/**
* @var string
*/
private $baseDir;
/**
* @param string $baseDir
*/
function __construct($baseDir) {
$this->baseDir = $baseDir;
}
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* E.g.:
* - The class we look for is Some\Namespace\Some\Class
* - The file is actually in "exotic/location.php". This is not following
* PSR-0 or PEAR standard, so we need a plugin.
* -> The class finder will transform the class name to
* "Some/Namespace/Some/Class.php"
* - The plugin was registered for the namespace "Some\Namespace". This is
* because all those exotic classes all begin with Some\Namespace\
* -> The arguments will be:
* ($api = the API object, see below)
* $logical_base_path = "Some/Namespace/"
* $relative_path = "Some/Class.php"
* $api->getClass() gives the original class name, if we still need it.
* -> We are supposed to:
* if ($api->suggestFile('exotic/location.php')) {
* return TRUE;
* }
*
* @param InjectedApiInterface $api
* An object with a suggestFile() method.
* We are supposed to suggest files until suggestFile() returns TRUE, or we
* have no more suggestions.
* @param string $logical_base_path_empty
* The key that this plugin was registered with.
* With trailing '/'.
* @param string $relative_path_irrelevant
* Second part of the canonical path, ending with '.php'.
*
* @return bool|null
* TRUE, if the file was found.
* FALSE or NULL, otherwise.
*/
function findFile($api, $logical_base_path_empty, $relative_path_irrelevant) {
$q = db_select('registry');
// Use LIKE here to make the query case-insensitive.
$q->condition('name', db_like($api->getClass()), 'LIKE');
$q->addField('registry', 'filename');
$stmt = $q->execute();
while ($relative_path = $stmt->fetchField()) {
$file = $this->baseDir . $relative_path;
// Attention: The db_select() above can trigger the class loader for
// classes and interfaces of the database layer. This can cause some files
// to be included twice, if the file defines more than one class.
// So we need to use require_once here, instead of require. That is, use
// guessFile() instead of claimFile().
if ($api->guessFile($file)) {
return TRUE;
}
}
return FALSE;
}
}

View File

@ -0,0 +1,219 @@
<?php
namespace Drupal\xautoload\ClassFinder\Plugin;
use Drupal\xautoload\ClassFinder\GenericPrefixMap;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
/**
* There are different dimensions of state for each module:
*
* 1) Classes outside of Drupal\\$modulename\\Tests\\
* a) We don't know yet whether these classes are using PSR-0, PSR-4,
* PEAR-Flat, or none of these.
* b) We know these classes use PSR-0 only.
* c) We know these classes use PSR-4 only.
* d) We know these classes use PEAR-Flat only.
*
* 2) Classes inside Drupal\\$modulename\\Tests\\
* a) We don't know yet whether these classes are using PSR-0, PSR-4, or none
* of these.
* b) We know these classes all use PSR-0.
* c) We know these classes all use PSR-4.
*
* Any combination of a state from (1) with a state from (2) is possible.
*
* The state could even change during the execution of the findClass() method,
* due to another autoloader instance being fired during a file inclusion, e.g.
* for a base class.
*/
class DrupalExtensionNamespaceFinderPlugin implements FinderPluginInterface {
/**
* @var string
* E.g. 'theme' or 'module'.
*/
protected $type;
/**
* Namespace map used in the class finder for PSR-0/4-like mappings.
*
* @var GenericPrefixMap
*/
protected $namespaceMap;
/**
* Prefix map used in the class finder for PEAR-like mappings.
*
* @var GenericPrefixMap
*/
protected $prefixMap;
/**
* Directory behavior for PSR-4.
*
* @var DefaultDirectoryBehavior
*/
protected $defaultBehavior;
/**
* Directory behavior with the special underscore handling for PSR-0.
*
* @var Psr0DirectoryBehavior
*/
protected $psr0Behavior;
/**
* @var DrupalSystemInterface
*/
protected $system;
/**
* @param string $type
* E.g. 'theme' or 'module'.
* @param GenericPrefixMap $namespace_map
* @param GenericPrefixMap $prefix_map
* @param DrupalSystemInterface $system
*/
function __construct($type, $namespace_map, $prefix_map, $system) {
$this->type = $type;
$this->prefixMap = $prefix_map;
$this->namespaceMap = $namespace_map;
$this->defaultBehavior = new DefaultDirectoryBehavior();
$this->psr0Behavior = new Psr0DirectoryBehavior();
$this->system = $system;
}
/**
* Looks up a class starting with "Drupal\$extension_name\\".
*
* This plugin method will be called for every class beginning with
* "Drupal\\$extension_name\\", as long as the plugin is registered for
* $logical_base_path = 'Drupal/$extension_name/'.
*
* A similar plugin will is registered along with this one for the PEAR-FLAT
* pattern, called for every class beginning with $modulename . '_'.
*
* The plugin will eventually unregister itself and its cousin, once it has
* - determined the correct path for the module, and
* - determined that the module is using either PSR-0 or PSR-4.
* It does that by including the file candidate for PSR-0 and/or PSR-4 and
* checking whether the class is now defined.
*
* The plugin will instead register a direct
*
* @param \Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface $api
* An object with methods like suggestFile() and guessFile().
* @param string $logical_base_path
* The logical base path determined from the registered namespace.
* E.g. 'Drupal/menupoly/'.
* @param string $relative_path
* Remaining part of the logical path following $logical_base_path.
* E.g. 'FooNamespace/BarClass.php'.
* @param string|null $extension_name
* Second key that the plugin was registered with. Usually this would be the
* physical base directory where we prepend the relative path to get the
* file path. But in this case it is simply the extensions name.
* E.g. 'menupoly'.
*
* @return bool|null
* TRUE, if the file was found.
* FALSE or NULL, otherwise.
*/
function findFile($api, $logical_base_path, $relative_path, $extension_name = NULL) {
$extension_file = $this->system->drupalGetFilename($this->type, $extension_name);
if (empty($extension_file)) {
// Extension does not exist, or is not installed.
return FALSE;
}
$nspath = 'Drupal/' . $extension_name . '/';
$testpath = $nspath . 'Tests/';
$uspath = $extension_name . '/';
$extension_dir = dirname($extension_file);
$src = $extension_dir . '/src/';
$lib_psr0 = $extension_dir . '/lib/Drupal/' . $extension_name . '/';
$is_test_class = (0 === strpos($relative_path, 'Tests/'));
// Try PSR-4.
if ($api->guessPath($src . $relative_path)) {
if ($is_test_class) {
// Register PSR-0 directory for "Drupal\\$modulename\\Tests\\"
// This generally happens only once per module, because for subsequent
// test classes the class will be found before this plugin is triggered.
// However, for class_exists() with nonexistent test files, this line
// will occur more than once.
$this->namespaceMap->registerDeepPath($testpath, $src . 'Tests/', $this->defaultBehavior);
// We found the class, but it is a test class, so it does not tell us
// anything about whether non-test classes are in PSR-0 or PSR-4.
return TRUE;
}
// Register PSR-4 directory for "Drupal\\$modulename\\".
$this->namespaceMap->registerDeepPath($nspath, $src, $this->defaultBehavior);
// Unregister the lazy plugins, including this one, for
// "Drupal\\$modulename\\" and for $modulename . '_'.
$this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
$this->prefixMap->unregisterDeepPath($uspath, $extension_name);
// Test classes in PSR-4 are already covered by the PSR-4 plugin we just
// registered. But test classes in PSR-0 would slip through. So we check
// if a separate behavior needs to be registered for those.
if (is_dir($lib_psr0 . 'Tests/')) {
$this->namespaceMap->registerDeepPath($testpath, $lib_psr0 . 'Tests/', $this->psr0Behavior);
}
// The class was found, so return TRUE.
return TRUE;
}
// Build PSR-0 relative path.
if (FALSE === $nspos = strrpos($relative_path, '/')) {
// No namespace separators in $relative_path, so all underscores must be
// replaced.
$relative_path = str_replace('_', '/', $relative_path);
}
else {
// Replace only those underscores in $relative_path after the last
// namespace separator, from right to left. On average there is no or very
// few of them, so this loop rarely iterates even once.
while ($nspos < $uspos = strrpos($relative_path, '_')) {
$relative_path[$uspos] = '/';
}
}
// Try PSR-0
if ($api->guessPath($lib_psr0 . $relative_path)) {
if ($is_test_class) {
// We know now that there are test classes using PSR-0.
$this->namespaceMap->registerDeepPath($testpath, $lib_psr0 . 'Tests/', $this->psr0Behavior);
// We found the class, but it is a test class, so it does not tell us
// anything about whether non-test classes are in PSR-0 or PSR-4.
return TRUE;
}
// Unregister the lazy plugins, including this one.
$this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
$this->prefixMap->unregisterDeepPath($uspath, $extension_name);
// Register PSR-0 for regular namespaced classes.
$this->namespaceMap->registerDeepPath($nspath, $lib_psr0, $this->psr0Behavior);
// Test classes in PSR-0 are already covered by the PSR-0 plugin we just
// registered. But test classes in PSR-4 would slip through. So we check
// if a separate behavior needs to be registered for those.
# if (is_dir($src . 'Tests/')) {
# $this->namespaceMap->registerDeepPath($testpath, $src . 'Tests/', $this->psr0Behavior);
# }
// The class was found, so return TRUE.
return TRUE;
}
return FALSE;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Drupal\xautoload\ClassFinder\Plugin;
class DrupalExtensionUnderscoreFinderPlugin extends DrupalExtensionNamespaceFinderPlugin {
/**
* {@inheritdoc}
*/
function findFile($api, $logical_base_path, $relative_path, $extension_name = NULL) {
$extension_file = $this->system->drupalGetFilename($this->type, $extension_name);
if (empty($extension_file)) {
// Extension does not exist, or is not installed.
return FALSE;
}
$nspath = 'Drupal/' . $extension_name . '/';
$testpath = $nspath . 'Tests/';
$uspath = $extension_name . '/';
$lib = dirname($extension_file) . '/lib/';
$lib_psr0 = $lib . 'Drupal/' . $extension_name . '/';
// Try PEAR-Flat.
if ($api->guessPath($lib . $relative_path)) {
// Register PEAR-Flat.
$this->prefixMap->registerDeepPath($uspath, $lib, $this->defaultBehavior);
// Unregister the lazy plugins.
$this->namespaceMap->unregisterDeepPath($nspath, $extension_name);
$this->prefixMap->unregisterDeepPath($uspath, $extension_name);
// See if there are PSR-0 or PSR-4 test classes.
if (is_dir($lib_psr0 . 'Tests/')) {
$this->namespaceMap->registerDeepPath(
$testpath,
$lib_psr0 . 'Tests/',
$this->psr0Behavior);
}
if (is_dir($lib . 'Tests/')) {
$this->namespaceMap->registerDeepPath(
$testpath,
$lib . 'Tests/',
$this->defaultBehavior);
}
// The class was found, so return TRUE.
return TRUE;
}
// The class was not found, so return FALSE.
return FALSE;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Drupal\xautoload\ClassFinder\Plugin;
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
use xautoload_FinderPlugin_Interface;
/**
* X Autoload plugins are for:
* - More exotic autoload patterns that are incompatible with PSR-0 or PEAR
* - Situations where we don't want to register a ton of namespaces, and using
* a plugin instead gives us performance benefits.
*/
interface FinderPluginInterface extends xautoload_FinderPlugin_Interface {
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* E.g.:
* - The class we look for is Some\Namespace\Some\Class
* - The file is actually in "exotic/location.php". This is not following
* PSR-0 or PEAR standard, so we need a plugin.
* -> The class finder will transform the class name to
* "Some/Namespace/Some/Class.php"
* - The plugin was registered for the namespace "Some\Namespace". This is
* because all those exotic classes all begin with Some\Namespace\
* -> The arguments will be:
* ($api = the API object, see below)
* $path_fragment = "Some/Namespace/"
* $path_suffix = "Some/Class.php"
* $api->getClass() gives the original class name, if we still need it.
* -> We are supposed to:
* if ($api->suggestFile('exotic/location.php')) {
* return TRUE;
* }
*
* @param InjectedApiInterface $api
* An object with a suggestFile() method.
* We are supposed to suggest files until suggestFile() returns TRUE, or we
* have no more suggestions.
* @param string $path_fragment
* The key that this plugin was registered with.
* With trailing '/'.
* @param string $path_suffix
* Second part of the canonical path, ending with '.php'.
* @param int|string $id
* Id under which the plugin was registered.
* This may be a numeric id, or a string key.
*
* @return bool|null
* TRUE, if the file was found.
* FALSE, otherwise.
*
* NOTE:
* The signature of this method has changed since the legacy base interface,
* with a new optional parameter being added.
* Due to a bug in PHP 5.3.0 - 5.3.8, redeclaring the method with the
* modified signature would result in a fatal error in these PHP versions.
* This is why the method is commented out.
* The additional optional parameter can still be added in implementations.
*/
# function findFile($api, $path_fragment, $path_suffix, $id = NULL);
}

View File

@ -0,0 +1,34 @@
<?php
namespace Drupal\xautoload\ClassFinder\Plugin;
use Drupal\xautoload\ClassFinder\InjectedApi\FindFileInjectedApi;
class Psr4FinderPlugin implements FinderPluginInterface {
/**
* @param FindFileInjectedApi $api
* An object with a suggestFile() method.
* We are supposed to suggest files until suggestFile() returns TRUE, or we
* have no more suggestions.
* @param string $logical_base_path
* The key that this plugin was registered with.
* With trailing '/'.
* @param string $relative_path
* Second part of the canonical path, ending with '.php'.
* @param int|string $base_dir
* Id under which the plugin was registered.
* This should be the PSR-4 base directory.
*
* @return bool|null
* TRUE, if the file was found.
* FALSE, otherwise.
*/
function findFile($api, $logical_base_path, $relative_path, $base_dir = NULL) {
// The $relative_path has the replacements from PSR-0, which we don't want.
// So we need to re-calculate it.
$relative_classname = substr($api->getClass(), strlen($logical_base_path));
$relative_path = str_replace('\\', '/', $relative_classname) . '.php';
return $api->suggestFile($base_dir . '/' . $relative_path);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Drupal\xautoload\ClassFinder;
use Drupal\xautoload\ClassLoader\AbstractClassLoader;
use Drupal\xautoload\CacheMissObserver\CacheMissObserverInterface;
/**
* A placeholder class finder. Used to postpone expensive operations until they
* are actually needed.
*/
class ProxyClassFinder extends AbstractClassLoader implements ClassFinderInterface {
/**
* @var ExtendedClassFinderInterface
* The actual class finder.
*/
protected $finder;
/**
* @var CacheMissObserverInterface[]
* Operations to run when the actual finder is initialized.
*/
protected $cacheMissObservers = array();
/**
* @var bool
*/
protected $initialized = FALSE;
/**
* @param ExtendedClassFinderInterface $finder
*
* @internal param \Drupal\xautoload\Adapter\DrupalExtensionAdapter $helper
*/
function __construct($finder) {
$this->finder = $finder;
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
$this->initFinder();
$this->finder->loadClass($class);
}
/**
* {@inheritdoc}
*/
function apiFindFile($api, $class) {
$this->initFinder();
return $this->finder->apiFindFile($api, $class);
}
/**
* @param CacheMissObserverInterface $observer
*/
function observeFirstCacheMiss($observer) {
if (!$this->initialized) {
$this->cacheMissObservers[] = $observer;
}
else {
$observer->cacheMiss($this->finder);
}
}
/**
* @return ClassFinderInterface
*/
function getFinder() {
$this->initFinder();
return $this->finder;
}
/**
* Initialize the finder and notify cache miss observers.
*/
protected function initFinder() {
if (!$this->initialized) {
$this->initialized = TRUE;
foreach ($this->cacheMissObservers as $operation) {
$operation->cacheMiss($this->finder);
}
}
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\ClassFinderInterface;
use Drupal\xautoload\CacheManager\CacheManager;
abstract class AbstractCachedClassLoader
extends AbstractClassLoaderDecorator
implements CacheManagerObserverInterface {
/**
* @var string
*/
protected $prefix;
/**
* This method has side effects, so it is not the constructor.
*
* @param ClassFinderInterface $finder
* @param CacheManager $cacheManager
*
* @return self
*
* @throws \Exception
*/
static function create($finder, $cacheManager) {
/** @var self $loader */
$loader = new static($finder);
if (!$loader->checkRequirements()) {
$class = get_class($loader);
throw new CacheNotSupportedException("Unable to use $class, because the respetive PHP extension is not enabled.");
}
$cacheManager->observeCachePrefix($loader);
return $loader;
}
/**
* @return bool
*/
protected abstract function checkRequirements();
/**
* {@inheritdoc}
*/
function setCachePrefix($prefix) {
$this->prefix = $prefix;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Drupal\xautoload\ClassLoader;
/**
* Behaves mostly like the Symfony ClassLoader classes.
*/
abstract class AbstractClassLoader implements ClassLoaderInterface {
/**
* Registers this instance as an autoloader.
*
* @param boolean $prepend
* If TRUE, the loader will be prepended. Otherwise, it will be appended.
*/
function register($prepend = FALSE) {
// http://www.php.net/manual/de/function.spl-autoload-register.php#107362
// "when specifying the third parameter (prepend), the function will fail badly in PHP 5.2"
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
spl_autoload_register(array($this, 'loadClass'), TRUE, $prepend);
}
elseif ($prepend) {
$loaders = spl_autoload_functions();
spl_autoload_register(array($this, 'loadClass'));
foreach ($loaders as $loader) {
spl_autoload_unregister($loader);
spl_autoload_register($loader);
}
}
else {
spl_autoload_register(array($this, 'loadClass'));
}
}
/**
* Unregister from the spl autoload stack.
*/
function unregister() {
spl_autoload_unregister(array($this, 'loadClass'));
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\ClassFinder\ClassFinderInterface;
/**
* Behaves mostly like the Symfony ClassLoader classes.
*/
abstract class AbstractClassLoaderDecorator extends AbstractClassLoader {
/**
* @var ClassFinderInterface
*/
protected $finder;
/**
* @param ClassFinderInterface $finder
* The object that does the actual class finding.
*/
protected function __construct($finder) {
$this->finder = $finder;
}
/**
* Replace the finder with another one.
*
* @param ClassFinderInterface $finder
* The object that does the actual class finding.
*/
function setFinder($finder) {
$this->finder = $finder;
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
$this->finder->loadClass($class);
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManager;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\ClassFinderInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
/**
* Bass class for cached class loader decorators where cache entries cannot be
* written one by one, but have to be written all at once instead.
*
* Saving the cache immediately on every cache miss would be too expensive. On
* the other hand, saving only at the end of the request might fail if the
* request does not end properly, or if some classes are still loaded after the
* end-of-process callback.
*
* The solution is an exponentially growing queue. Cache writing happens not on
* every cache miss, but only on the 1st, 3rd, 7th, 15th, 31st, 63rd etc.
*
* This will result in a "hot" cache after a limited number of requests, and
* with a limited number of cache write operations.
*/
abstract class AbstractQueuedCachedClassLoader
extends AbstractClassLoaderDecorator
implements CacheManagerObserverInterface {
/**
* @var int
*/
private $nMax = 1;
/**
* @var int
*/
private $n = 0;
/**
* @var string[]
*/
private $toBeDeleted = array();
/**
* @var string[]
*/
private $toBeAdded = array();
/**
* @var string[]
*/
private $classFiles;
/**
* This method has side effects, so it is not the constructor.
*
* @param ClassFinderInterface $finder
* @param CacheManager $cacheManager
*
* @return self
*
* @throws \Exception
*/
static function create($finder, $cacheManager) {
/** @var self $loader */
$loader = new static($finder);
$cacheManager->observeCachePrefix($loader);
return $loader;
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Look if the cache has anything for this class.
if (isset($this->classFiles[$class])) {
$file = $this->classFiles[$class];
// The is_file() check may cost around 0.0045 ms per class file, but this
// depends on your system of course.
if (is_file($file)) {
require $file;
return;
}
$this->toBeDeleted[$class] = $file;
unset($this->classFiles[$class]);
++$this->n;
}
// Resolve cache miss.
$api = new LoadClassGetFileInjectedApi($class);
if ($this->finder->apiFindFile($api, $class)) {
// Queue the result for the cache.
$this->toBeAdded[$class]
= $this->classFiles[$class]
= $api->getFile();
++$this->n;
}
// Save the cache if enough has been queued up.
if ($this->n >= $this->nMax) {
$this->classFiles = $this->updateClassFiles($this->toBeAdded, $this->toBeDeleted);
$this->toBeDeleted = array();
$this->toBeAdded = array();
$this->nMax *= 2;
$this->n = 0;
}
}
/**
* Set the new cache prefix after a flush cache.
*
* @param string $prefix
* A prefix for the storage key in APC.
*/
function setCachePrefix($prefix) {
$this->classFiles = $this->loadClassFiles($prefix);
}
/**
* @param string $prefix
*
* @return string[]
*/
abstract protected function loadClassFiles($prefix);
/**
* @param string[] $toBeAdded
* @param string[] $toBeRemoved
*
* @return string[]
*/
abstract protected function updateClassFiles($toBeAdded, $toBeRemoved);
}

View File

@ -0,0 +1,39 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
class ApcClassLoader extends AbstractCachedClassLoader implements CacheManagerObserverInterface {
/**
* @return bool
*/
protected function checkRequirements() {
return extension_loaded('apc') && function_exists('apc_store');
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Look if the cache has anything for this class.
if ($file = apc_fetch($this->prefix . $class)) {
if (is_file($file)) {
require $file;
return;
}
apc_delete($this->prefix . $class);
}
// Resolve cache miss.
$api = new LoadClassGetFileInjectedApi($class);
if ($this->finder->apiFindFile($api, $class)) {
apc_store($this->prefix . $class, $api->getFile());
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
class ApcuClassLoader extends AbstractCachedClassLoader implements CacheManagerObserverInterface {
/**
* @return bool
*/
protected function checkRequirements() {
return extension_loaded('apcu') && function_exists('apcu_store');
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Look if the cache has anything for this class.
if ($file = \apcu_fetch($this->prefix . $class)) {
if (is_file($file)) {
require $file;
return;
}
\apcu_delete($this->prefix . $class);
}
// Resolve cache miss.
$api = new LoadClassGetFileInjectedApi($class);
if ($this->finder->apiFindFile($api, $class)) {
\apcu_store($this->prefix . $class, $api->getFile());
}
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Drupal\xautoload\ClassLoader;
class ApcuQueuedCachedClassLoader extends AbstractQueuedCachedClassLoader {
/**
* @var string
*/
private $prefix;
/**
* @param string $prefix
*
* @return string[]
*/
protected function loadClassFiles($prefix) {
$this->prefix = $prefix;
$cached = \apcu_fetch($this->prefix);
return !empty($cached)
? $cached
: array();
}
/**
* @param string[] $toBeAdded
* @param string[] $toBeRemoved
*
* @return string[]
*/
protected function updateClassFiles($toBeAdded, $toBeRemoved) {
$class_files = $toBeAdded;
// Other requests may have already written to the cache, so we get an up to
// date version.
$cached = \apcu_fetch($this->prefix);
if (!empty($cached)) {
$class_files += $cached;
foreach ($toBeRemoved as $class => $file) {
if (isset($class_files[$class]) && $class_files[$class] === $file) {
unset($class_files[$class]);
}
}
}
\apcu_store($this->prefix, $class_files);
return $class_files;
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Drupal\xautoload\ClassLoader;
class CacheNotSupportedException extends \Exception {}

View File

@ -0,0 +1,30 @@
<?php
namespace Drupal\xautoload\ClassLoader;
/**
* Behaves mostly like the Symfony ClassLoader classes.
*/
interface ClassLoaderInterface {
/**
* Registers this instance as an autoloader.
*
* @param boolean $prepend
* If TRUE, the loader will be prepended. Otherwise, it will be appended.
*/
function register($prepend = FALSE);
/**
* Unregister this instance as an autoloader.
*/
function unregister();
/**
* Callback for class loading. This will include ("require") the file found.
*
* @param string $class
* The class to load.
*/
function loadClass($class);
}

View File

@ -0,0 +1,58 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManager;
use Drupal\xautoload\ClassFinder\ClassFinderInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
/**
* A class loader decorator using Drupal's native db cache.
*/
class DbCacheClassLoader extends AbstractQueuedCachedClassLoader {
/**
* @var string
*/
private $cacheName;
/**
* @param string $prefix
*
* @return string[]
*/
protected function loadClassFiles($prefix) {
$this->cacheName = 'xautoload_db_cache:' . $prefix;
$cached = cache_get($this->cacheName);
return isset($cached->data)
? $cached->data
: array();
}
/**
* @param string[] $toBeAdded
* @param string[] $toBeRemoved
*
* @return string[]
*/
protected function updateClassFiles($toBeAdded, $toBeRemoved) {
$class_files = $toBeAdded;
// Other requests may have already written to the cache, so we get an up to
// date version.
$cached = cache_get($this->cacheName);
if (isset($cached->data)) {
$class_files += $cached->data;
foreach ($toBeRemoved as $class => $file) {
if (isset($class_files[$class]) && $class_files[$class] === $file) {
unset($class_files[$class]);
}
}
}
cache_set($this->cacheName, $class_files);
return $class_files;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
class WinCacheClassLoader extends AbstractCachedClassLoader implements CacheManagerObserverInterface {
/**
* @return bool
*/
protected function checkRequirements() {
return extension_loaded('wincache')
&& function_exists('wincache_ucache_get');
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Look if the cache has anything for this class.
if ($file = wincache_ucache_get($this->prefix . $class)) {
if (is_file($file)) {
require $file;
return;
}
wincache_ucache_delete($this->prefix . $class);
}
// Resolve cache miss.
$api = new LoadClassGetFileInjectedApi($class);
if ($this->finder->apiFindFile($api, $class)) {
wincache_ucache_set($this->prefix . $class, $api->getFile());
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Drupal\xautoload\ClassLoader;
use Drupal\xautoload\CacheManager\CacheManagerObserverInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\LoadClassGetFileInjectedApi;
class XCacheClassLoader extends AbstractCachedClassLoader implements CacheManagerObserverInterface {
/**
* @return bool
*/
protected function checkRequirements() {
return extension_loaded('Xcache') && function_exists('xcache_isset');
}
/**
* {@inheritdoc}
*/
function loadClass($class) {
// Look if the cache has anything for this class.
if (xcache_isset($this->prefix . $class)
&& $file = xcache_get($this->prefix . $class)
) {
if (is_file($file)) {
require $file;
return;
}
xcache_unset($this->prefix . $class);
}
// Resolve cache miss.
$api = new LoadClassGetFileInjectedApi($class);
if ($this->finder->apiFindFile($api, $class)) {
xcache_set($this->prefix . $class, $api->getFile());
}
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Drupal\xautoload\DIC;
/**
* @see ServiceFactory
*/
class ServiceContainer implements ServiceContainerInterface {
/**
* @var ServiceFactory
*/
protected $factory;
/**
* @var object[]
*/
protected $services = array();
/**
* @param string $key
*
* @return mixed
*/
function get($key) {
return isset($this->services[$key])
? $this->services[$key]
: $this->services[$key] = $this->factory->$key($this) ? : FALSE;
}
/**
* Unset the service for a specific key.
*
* @param string $key
*/
function reset($key) {
$this->services[$key] = NULL;
}
/**
* Register a new service under the given key.
*
* @param string $key
* @param mixed $service
*/
function set($key, $service) {
$this->services[$key] = $service;
}
/**
* Magic getter for a service.
*
* @param string $key
*
* @return mixed
*
* @throws \Exception
*/
function __get($key) {
if (isset($this->services[$key])) {
return $this->services[$key];
}
if (!method_exists($this->factory, $key)) {
throw new \Exception("Unsupported key '$key' for service factory.");
}
return $this->services[$key] = $this->factory->$key($this) ? : FALSE;
}
/**
* @param ServiceFactory $factory
*/
function __construct($factory) {
$this->factory = $factory;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\xautoload\DIC;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
use Drupal\xautoload\Adapter\DrupalExtensionAdapter;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\CacheManager\CacheManager;
use Drupal\xautoload\ClassFinder\ProxyClassFinder;
use Drupal\xautoload\Discovery\ClassMapGenerator;
use Drupal\xautoload\Discovery\ClassMapGeneratorInterface;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\Libraries\LibrariesInfoAlter;
use Drupal\xautoload\Phases\DrupalPhaseControl;
use Drupal\xautoload\Phases\ExtensionNamespaces;
use Drupal\xautoload\Main;
/**
* @property Main $main
* @property ClassFinderAdapter $adapter
* @property ClassMapGeneratorInterface $classMapGenerator
* @property ClassMapGenerator $classMapGeneratorRaw
* @property CacheManager $cacheManager
* @property ProxyClassFinder $proxyFinder
* @property ExtendedClassFinderInterface $classFinder
* @property ExtendedClassFinderInterface $finder
* Alias for ->classFinder
* @property DrupalSystemInterface $system
* @property DrupalPhaseControl $phaseControl
* @property DrupalExtensionAdapter $extensionRegistrationService
* @property ExtensionNamespaces extensionNamespaces
* @property LibrariesInfoAlter librariesInfoAlter
*
* @see \Drupal\xautoload\DIC\ServiceContainer
* @see \Drupal\xautoload\DIC\ServiceFactory
*/
interface ServiceContainerInterface {
/**
* Retrieves a lazy-instantiated service.
*
* @param string $key
* A key to specify a service.
* @return mixed
* The service for the given key. Usually an object.
*/
function __get($key);
}

View File

@ -0,0 +1,174 @@
<?php
namespace Drupal\xautoload\DIC;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
use Drupal\xautoload\Adapter\DrupalExtensionAdapter;
use Drupal\xautoload\ClassFinder\ClassFinder;
use Drupal\xautoload\ClassFinder\ClassFinderInterface;
use Drupal\xautoload\CacheManager\CacheManager;
use Drupal\xautoload\ClassFinder\ProxyClassFinder;
use Drupal\xautoload\Discovery\CachedClassMapGenerator;
use Drupal\xautoload\Discovery\ClassMapGenerator;
use Drupal\xautoload\DrupalSystem\DrupalSystem;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\Libraries\LibrariesInfoAlter;
use Drupal\xautoload\Phases\DrupalCoreRegistryRegistrator;
use Drupal\xautoload\Phases\DrupalPhaseControl;
use Drupal\xautoload\Phases\ExtensionNamespaces;
use Drupal\xautoload\Phases\HookXautoload;
use Drupal\xautoload\Libraries\LibrariesOnInit;
use Drupal\xautoload\Main;
/**
* @see ServiceContainerInterface
* @see ServiceContainer
*/
class ServiceFactory {
/**
* @param ServiceContainer $services
*
* @return Main
*/
function main($services) {
return new Main($services);
}
/**
* @param ServiceContainer $services
*
* @return ClassFinderAdapter
*/
function adapter($services) {
return new ClassFinderAdapter($services->finder, $services->classMapGenerator);
}
/**
* @param ServiceContainer $services
*
* @return ClassMapGenerator
*/
function classMapGenerator($services) {
return new CachedClassMapGenerator($services->classMapGeneratorRaw, $services->system);
}
/**
* @param ServiceContainer $services
*
* @return ClassMapGenerator
*/
function classMapGeneratorRaw($services) {
return new ClassMapGenerator();
}
/**
* @param ServiceContainer $services
*
* @return DrupalExtensionAdapter
*/
function extensionRegistrationService($services) {
return new DrupalExtensionAdapter($services->system, $services->finder);
}
/**
* @param ServiceContainer $services
*
* @return CacheManager
*/
function cacheManager($services) {
return CacheManager::create($services->system);
}
/**
* Proxy class finder.
*
* @param ServiceContainer $services
*
* @return ClassFinderInterface
* Proxy object wrapping the class finder.
* This is used to delay namespace registration until the first time the
* finder is actually used.
* (which might never happen thanks to the APC cache)
*/
function proxyFinder($services) {
// The class finder is cheap to create, so it can use an identity proxy.
return new ProxyClassFinder($services->finder);
}
/**
* The class finder (alias for 'finder').
*
* @param ServiceContainer $services
*
* @return ClassFinderInterface
* Object that can find classes,
* and provides methods to register namespaces and prefixes.
* Note: The findClass() method expects an InjectedAPI argument.
*/
function classFinder($services) {
return $services->finder;
}
/**
* The class finder (alias for 'classFinder').
*
* @param ServiceContainer $services
*
* @return ClassFinderInterface
* Object that can find classes,
* and provides methods to register namespaces and prefixes.
* Notes:
* - The findClass() method expects an InjectedAPI argument.
* - namespaces are only supported since PHP 5.3
*/
function finder($services) {
return new ClassFinder();
}
/**
* @param ServiceContainer $services
*
* @return DrupalSystemInterface
*/
function system($services) {
return new DrupalSystem();
}
/**
* @param ServiceContainer $services
*
* @return DrupalPhaseControl
*/
function phaseControl($services) {
$observers = array(
$services->extensionNamespaces,
new HookXautoload($services->system),
new LibrariesOnInit($services->system),
);
if ($services->system->variableGet(XAUTOLOAD_VARNAME_REPLACE_CORE, FALSE)) {
$observers[] = new DrupalCoreRegistryRegistrator();
}
return new DrupalPhaseControl($services->system, $observers);
}
/**
* @param ServiceContainer $services
*
* @return ExtensionNamespaces
*/
function extensionNamespaces($services) {
return new ExtensionNamespaces($services->system);
}
/**
* @param ServiceContainer $services
*
* @return LibrariesInfoAlter
*/
function librariesInfoAlter($services) {
return new LibrariesInfoAlter();
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Drupal\xautoload\DirectoryBehavior;
/**
* Directory behavior for PSR-4 and PEAR.
*
* This class is a marker only, to be checked with instanceof.
* @see \Drupal\xautoload\ClassFinder\GenericPrefixMap::loadClass()
*/
final class DefaultDirectoryBehavior implements DirectoryBehaviorInterface {
}

View File

@ -0,0 +1,9 @@
<?php
namespace Drupal\xautoload\DirectoryBehavior;
interface DirectoryBehaviorInterface {
}

View File

@ -0,0 +1,13 @@
<?php
namespace Drupal\xautoload\DirectoryBehavior;
/**
* Directory behavior for PSR-0.
*
* This class is a marker only, to be checked with instanceof.
* @see \Drupal\xautoload\ClassFinder\GenericPrefixMap::loadClass()
*/
final class Psr0DirectoryBehavior implements DirectoryBehaviorInterface {
}

View File

@ -0,0 +1,45 @@
<?php
namespace Drupal\xautoload\Discovery;
class CachedClassMapGenerator implements ClassMapGeneratorInterface {
/**
* @var ClassMapGeneratorInterface
*/
protected $decorated;
/**
* @var \Drupal\xautoload\DrupalSystem\DrupalSystemInterface
*/
protected $system;
/**
* @param ClassMapGeneratorInterface $decorated
* @param \Drupal\xautoload\DrupalSystem\DrupalSystemInterface $system
*/
function __construct($decorated, $system) {
$this->decorated = $decorated;
$this->system = $system;
}
/**
* @param string[] $paths
*
* @return string[]
*/
function wildcardPathsToClassmap($paths) {
// Attempt to load from cache.
$cid = 'xautoload:wildcardPathsToClassmap:' . md5(serialize($paths));
$cache = $this->system->cacheGet($cid);
if ($cache && isset($cache->data)) {
return $cache->data;
}
// Resolve cache miss and save.
$map = $this->decorated->wildcardPathsToClassmap($paths);
$this->system->cacheSet($cid, $map);
return $map;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\xautoload\Discovery;
class ClassMapGenerator implements ClassMapGeneratorInterface {
/**
* @param string[] $paths
*
* @return string[]
*/
function wildcardPathsToClassmap($paths) {
$files = $this->wildcardPathsToFiles($paths);
return $this->filesToClassmap($files);
}
/**
* @param string[] $files
*
* @return string[]
*/
protected function filesToClassmap($files) {
$map = array();
foreach ($files as $file) {
$classes = FileInspector::inspectPhpFile($file);
foreach ($classes as $class) {
$map[$class] = $file;
}
}
return $map;
}
/**
* @param string[] $paths
*
* @return string[]
*/
protected function wildcardPathsToFiles($paths) {
$wildcardFinder = new WildcardFileFinder();
$wildcardFinder->addPaths($paths);
return $wildcardFinder->getFiles();
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Drupal\xautoload\Discovery;
interface ClassMapGeneratorInterface {
/**
* @param string[] $paths
*
* @return string[]
*/
function wildcardPathsToClassmap($paths);
}

View File

@ -0,0 +1,81 @@
<?php
namespace Drupal\xautoload\Discovery;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
class ComposerDir {
/**
* @var string
*/
protected $dir;
/**
* @param string $dir
*
* @return self
*
* @throws \Exception
*/
static function create($dir) {
if (!is_dir($dir)) {
throw new \Exception("Composer directory '$dir' does not exist.");
}
return new self($dir);
}
/**
* @param string $dir
*/
protected function __construct($dir) {
$this->dir = $dir;
}
/**
* @param ClassFinderAdapter $adapter
*/
function writeToAdapter($adapter) {
// PSR-0 namespaces / prefixes
if (is_file($this->dir . '/autoload_namespaces.php')) {
$prefixes = require $this->dir . '/autoload_namespaces.php';
if (!empty($prefixes)) {
$adapter->addMultiplePsr0($prefixes);
}
}
// PSR-4 namespaces
if (is_file($this->dir . '/autoload_psr4.php')) {
$map = require $this->dir . '/autoload_psr4.php';
if (!empty($map)) {
$adapter->addMultiplePsr4($map);
}
}
// Class map
if (is_file($this->dir . '/autoload_classmap.php')) {
$class_map = require $this->dir . '/autoload_classmap.php';
if (!empty($class_map)) {
$adapter->addClassMap($class_map);
}
}
// Include path
if (is_file($this->dir . '/include_paths.php')) {
$include_paths = require $this->dir . '/include_paths.php';
if (!empty($include_paths)) {
array_push($include_paths, get_include_path());
set_include_path(join(PATH_SEPARATOR, $include_paths));
}
}
// Include files
if (is_file($this->dir . '/autoload_files.php')) {
$include_files = require $this->dir . '/autoload_files.php';
foreach ($include_files as $file) {
require $file;
}
}
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Drupal\xautoload\Discovery;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
class ComposerJson {
/**
* @var array
*/
protected $data;
/**
* @var string
*/
protected $pathPrefix;
/**
* @param string $file
*
* @return self
*
* @throws \Exception
*/
static function createFromFile($file) {
if (!file_exists($file)) {
throw new \Exception("File '$file' does not exist.");
}
$json = file_get_contents($file);
$data = json_decode($json, TRUE);
if (NULL === $data && JSON_ERROR_NONE !== json_last_error()) {
throw new \Exception("Invalid json in '$file'.");
}
return self::createFromData($data, dirname($file) . '/');
}
/**
* @param array $data
* @param string $path_prefix
*
* @return self
*
* @throws \Exception
*/
static function createFromData($data, $path_prefix) {
return empty($data['target-dir'])
? new self($data, $path_prefix)
: new ComposerJsonTargetDir($data, $path_prefix);
}
/**
* @param array $data
* @param string $path_prefix
*/
protected function __construct(array $data, $path_prefix) {
$this->data = $data;
$this->pathPrefix = $path_prefix;
}
/**
* @param ClassFinderAdapter $adapter
*/
function writeToAdapter(ClassFinderAdapter $adapter) {
$data = $this->data;
if (!empty($data['include-path'])) {
$this->addIncludePaths((array)$data['include-path']);
}
if (!empty($data['autoload']['psr-0'])) {
$map = $this->transformMultiple($data['autoload']['psr-0']);
$adapter->addMultiplePsr0($map);
}
if (!empty($data['autoload']['psr-4'])) {
$map = $this->transformMultiple($data['autoload']['psr-4']);
$adapter->addMultiplePsr4($map);
}
if (!empty($data['autoload']['classmap'])) {
$this->addClassmapSources($adapter, (array)$data['autoload']['classmap']);
}
if (!empty($data['autoload']['files'])) {
foreach ($data['autoload']['files'] as $file) {
require $this->pathPrefix . $file;
}
}
}
/**
* @param array $multiple
*
* @return array[]
*/
protected function transformMultiple(array $multiple) {
foreach ($multiple as &$paths) {
$paths = (array)$paths;
foreach ($paths as &$path) {
if ('' === $path || '/' !== $path[0]) {
$path = $this->pathPrefix . $path;
}
}
}
return $multiple;
}
/**
* @param string[] $include_paths
*/
protected function addIncludePaths(array $include_paths) {
foreach ($include_paths as &$path) {
$path = $this->pathPrefix . $path;
}
array_push($include_paths, get_include_path());
set_include_path(join(PATH_SEPARATOR, $include_paths));
}
/**
* @param ClassFinderAdapter $adapter
* @param string[] $sources_raw
* Array of files and folders to scan for class implementations.
*/
protected function addClassmapSources($adapter, array $sources_raw) {
foreach ($sources_raw as &$path) {
$path = $this->pathPrefix . $path;
}
$adapter->addClassmapSources($sources_raw);
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Drupal\xautoload\Discovery;
use Drupal\xautoload\Adapter\ClassFinderAdapter;
use Drupal\xautoload\DirectoryBehavior\DefaultDirectoryBehavior;
use Drupal\xautoload\DirectoryBehavior\Psr0DirectoryBehavior;
use Drupal\xautoload\Util;
class ComposerJsonTargetDir extends ComposerJson {
/**
* @var string
*/
protected $targetDir;
/**
* @param array $data
* @param string $path_prefix
*/
function __construct(array $data, $path_prefix) {
parent::__construct($data, $path_prefix);
$this->targetDir = rtrim($data['target-dir'], '/') . '/';
}
/**
* @param ClassFinderAdapter $adapter
*
* @throws \Exception
*/
function writeToAdapter(ClassFinderAdapter $adapter) {
$data = $this->data;
if (!empty($data['include-path'])) {
$paths = $this->pathsResolveTargetDir((array) $data['include-path']);
$this->addIncludePaths($paths, $this->pathPrefix);
}
if (!empty($data['autoload']['psr-0'])) {
$this->addMultipleWithTargetDir($adapter, $data['autoload']['psr-0']);
}
if (!empty($data['autoload']['psr-4'])) {
throw new \Exception("PSR-4 is incompatible with target-dir.");
}
if (!empty($data['autoload']['classmap'])) {
$paths = $this->pathsResolveTargetDir($data['autoload']['classmap']);
$this->addClassmapSources($adapter, $paths);
}
if (!empty($data['autoload']['files'])) {
$paths = $this->pathsResolveTargetDir($data['autoload']['files']);
foreach ($paths as $file) {
require $this->pathPrefix . $file;
}
}
}
/**
* @param string[] $paths
*
* @return string[]
*/
protected function pathsResolveTargetDir(array $paths) {
$strlen = strlen($this->targetDir);
foreach ($paths as &$path) {
if (0 === strpos($path, $this->targetDir)) {
$path = substr($path, $strlen);
}
}
return $paths;
}
/**
* @param ClassFinderAdapter $adapter
* @param array $prefixes
*/
protected function addMultipleWithTargetDir(ClassFinderAdapter $adapter, array $prefixes) {
$default_behavior = new DefaultDirectoryBehavior();
$psr0_behavior = new Psr0DirectoryBehavior();
$namespace_map = array();
$prefix_map = array();
$target_dir_strlen = strlen($this->targetDir);
foreach ($prefixes as $prefix => $paths) {
if (FALSE === strpos($prefix, '\\')) {
$logical_base_path = Util::prefixLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
if (0 !== strpos($deep_path, $this->targetDir)) {
continue;
}
$deep_path = $this->pathPrefix . substr($deep_path, $target_dir_strlen);
$prefix_map[$logical_base_path][$deep_path] = $default_behavior;
}
}
$logical_base_path = Util::namespaceLogicalPath($prefix);
foreach ((array) $paths as $root_path) {
$deep_path = strlen($root_path)
? rtrim($root_path, '/') . '/' . $logical_base_path
: $logical_base_path;
if (0 !== strpos($deep_path, $this->targetDir)) {
continue;
}
$deep_path = $this->pathPrefix . substr($deep_path, $target_dir_strlen);
$namespace_map[$logical_base_path][$deep_path] = $psr0_behavior;
}
}
if (!empty($prefix_map)) {
$adapter->getPrefixMap()->registerDeepPaths($prefix_map);
}
$adapter->getNamespaceMap()->registerDeepPaths($namespace_map);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Drupal\xautoload\Discovery;
use RuntimeException;
class FileInspector {
/**
* @param string $path
* @return string[]
* @throws RuntimeException
*/
static function inspectPhpFile($path) {
try {
$contents = php_strip_whitespace($path);
} catch (\Exception $e) {
throw new \RuntimeException(
'Could not scan for classes inside ' . $path . ": \n" . $e->getMessage(),
// The Exception code. Defaults to 0.
0,
// The previous exception used for exception chaining.
$e);
}
return self::inspectFileContents($contents);
}
/**
* @param string $contents
* The PHP file contents obtained with php_strip_whitespace($path).
*
* @return string[]
* Classes discovered in the file.
*/
protected static function inspectFileContents($contents) {
$traits = version_compare(PHP_VERSION, '5.4', '<')
? ''
: '|trait';
// return early if there is no chance of matching anything in this file
if (!preg_match('{\b(?:class|interface' . $traits . ')\s}i', $contents)) {
return array();
}
// strip heredocs/nowdocs
$contents = preg_replace(
'{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s',
'null',
$contents);
// strip strings
$contents = preg_replace(
'{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s',
'null',
$contents);
// strip leading non-php code if needed
if (substr($contents, 0, 2) !== '<?') {
$contents = preg_replace('{^.+?<\?}s', '<?', $contents);
}
// strip non-php blocks in the file
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
// strip trailing non-php code if needed
$pos = strrpos($contents, '?>');
if (FALSE !== $pos && FALSE === strpos(substr($contents, $pos), '<?')) {
$contents = substr($contents, 0, $pos);
}
preg_match_all(
'{
(?:
\b(?<![\$:>])(?P<type>class|interface' . $traits . ') \s+ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;]
)
}ix',
$contents,
$matches
);
$classes = array();
$namespace = '';
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
if (!empty($matches['ns'][$i])) {
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i])
. '\\';
}
else {
$classes[] = ltrim($namespace . $matches['name'][$i], '\\');
}
}
return $classes;
}
}

View File

@ -0,0 +1,211 @@
<?php
/**
* This file is autoloaded with the regular uncached xautoload.
*/
namespace Drupal\xautoload\Discovery;
/**
* Scan directories for wildcard files[] instructions in a module's info file.
*/
class WildcardFileFinder {
/**
* @var array
* Info array for the wildcard string currently being processed.
* This value changes for each new wildcard being processed.
*/
protected $value;
/**
* @var mixed[]
* $files array passed to hook_registry_files_alter().
*/
protected $files = array();
/**
* @param array $paths
* Array keys are file paths or wildcard file paths.
*/
function addDrupalPaths(array $paths) {
foreach ($paths as $path => $value) {
if (1
&& FALSE !== strpos($path, '*')
&& preg_match('#^([^\*]*)/(.*\*.*)$#', $path, $m)
) {
// Resolve wildcards.
$this->value = $value;
list(, $base, $wildcard) = $m;
$this->scanDirectory($base, $wildcard);
}
else {
// Register the file directly.
$this->files[$path] = $value;
}
}
}
/**
* @param string[] $paths
* Array keys are file paths or wildcard file paths.
* @param mixed $value
*/
function addPaths(array $paths, $value = TRUE) {
foreach ($paths as $path) {
if (1
&& FALSE !== strpos($path, '*')
&& preg_match('#^([^\*]*)/(.*\*.*)$#', $path, $m)
) {
// Resolve wildcards.
$this->value = $value;
list(, $base, $wildcard) = $m;
$this->scanDirectory($base, $wildcard);
}
elseif (is_dir($path)) {
// Resolve wildcards.
$this->value = $value;
$this->scanDirectory($path . '/', '**/*.inc');
$this->scanDirectory($path . '/', '**/*.php');
}
elseif (is_file($path)) {
// Register the file directly.
$this->files[$path] = $value;
}
}
}
/**
* @return string[]
*/
function getFiles() {
return array_keys($this->files);
}
/**
* @return mixed[]
*/
function getDrupalFiles() {
return $this->files;
}
/**
* @param string $dir
* Base folder, e.g. "sites/all/modules/foo/includes", which does NOT
* contain any asterisk ("*").
* @param string $wildcard
* Suffix which may contain asterisks.
*/
protected function scanDirectory($dir, $wildcard) {
if (!is_dir($dir)) {
return;
}
if (FALSE === strpos($wildcard, '*')) {
// $wildcard is a fixed string, not a wildcard.
$this->suggestFile($dir . '/' . $wildcard);
}
elseif ('**' === $wildcard) {
// Trick: "$a/**" == union of "$a/*" and "$a/*/**"
$this->scanDirectoryLevel($dir, '*');
$this->scanDirectoryLevel($dir, '*', '**');
}
elseif ('**/' === substr($wildcard, 0, 3)) {
// Trick: "$a/**/$b" == union of "$a/$b" and "$a/*/**/$b"
$remaining = substr($wildcard, 3);
$this->scanDirectory($dir, $remaining);
$this->scanDirectoryLevel($dir, '*', $wildcard);
}
elseif (FALSE !== ($slashpos = strpos($wildcard, '/'))) {
// $wildcard consists of more than one fragment.
$fragment = substr($wildcard, 0, $slashpos);
$remaining = substr($wildcard, $slashpos + 1);
if (FALSE === strpos($fragment, '*')) {
$this->scanDirectory($dir . '/' . $fragment, $remaining);
}
else {
$this->scanDirectoryLevel($dir, $fragment, $remaining);
}
}
else {
// $wildcard represents a file name.
$this->scanDirectoryLevel($dir, $wildcard);
}
}
/**
* @param string $dir
* Base directory, not containing any wildcard.
* @param string $fragment
* Wildcard path fragment to be processed now. This is never '**', but it
* always contains at least one asterisk.
* @param null $remaining
* Optional rest of the wildcard string, that may contain path fragments to
* be processed later.
*
* @throws \Exception
*/
protected function scanDirectoryLevel($dir, $fragment, $remaining = NULL) {
if (!is_dir($dir)) {
return;
}
if ('**' === $fragment) {
throw new \Exception("Fragment must not be '**'.");
}
foreach (scandir($dir) as $candidate) {
if (!$this->validateCandidate($candidate, $fragment)) {
continue;
}
if (!isset($remaining)) {
$this->suggestFile($dir . '/' . $candidate);
}
else {
$this->scanDirectory($dir . '/' . $candidate, $remaining);
}
}
}
/**
* @param $candidate
* String to be checked against the wildcard.
* @param $wildcard
* Wildcard string like '*', '*.*' or '*.inc'.
*
* @return bool
* TRUE, if $candidate matches $wildcard.
*/
protected function validateCandidate($candidate, $wildcard) {
if ($candidate == '.' || $candidate == '..') {
return FALSE;
}
if (strpos($candidate, '*') !== FALSE) {
return FALSE;
}
if ($wildcard == '*' || $wildcard == '**') {
return TRUE;
}
// More complex wildcard string.
$fragments = array();
foreach (explode('*', $wildcard) as $fragment) {
$fragments[] = preg_quote($fragment);
}
$regex = implode('.*', $fragments);
return preg_match("/^$regex$/", $candidate);
}
/**
* @param string $path
* Add a new file path to $this->filesInRegistry().
*/
protected function suggestFile($path) {
if (is_file($path)) {
$this->files[$path] = $this->value;
}
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace Drupal\xautoload\DrupalSystem;
class DrupalSystem implements DrupalSystemInterface {
function __construct() {
if (!function_exists('drupal_get_filename')) {
throw new \Exception("This class works only within a working Drupal environment.");
}
}
/**
* {@inheritdoc}
*/
function variableSet($name, $value) {
variable_set($name, $value);
}
/**
* {@inheritdoc}
*/
function variableGet($name, $default = NULL) {
return variable_get($name, $default);
}
/**
* {@inheritdoc}
*/
function drupalGetFilename($type, $name) {
return DRUPAL_ROOT . '/' . drupal_get_filename($type, $name);
}
/**
* {@inheritdoc}
*/
function drupalGetPath($type, $name) {
return DRUPAL_ROOT . '/' . drupal_get_path($type, $name);
}
/**
* {@inheritdoc}
*/
function getExtensionTypes($extension_names) {
$q = db_select('system');
$q->condition('name', $extension_names);
$q->fields('system', array('name', 'type'));
return $q->execute()->fetchAllKeyed();
}
/**
* {@inheritdoc}
*/
function getActiveExtensions() {
try {
// Doing this directly tends to be a lot faster than system_list().
return db_query("SELECT name, type from {system} WHERE status = 1")
->fetchAllKeyed();
}
catch (\DatabaseConnectionNotDefinedException $e) {
// During install, the database is not available.
// At this time only the system module is "installed".
/** See https://www.drupal.org/node/2393205 */
return array('system' => 'module');
}
catch (\PDOException $e) {
// Some time later during install, there is a database but not yet a system table.
// At this time only the system module is "installed".
// @todo Check if this is really a "Table 'system' doesn't exist'" exception.
return array('system' => 'module');
}
}
/**
* {@inheritdoc}
*/
function moduleImplements($hook) {
return module_implements($hook);
}
/**
* Wrapper for module_list()
*
* @return array
*/
function moduleList() {
return module_list();
}
/**
* @see libraries_info()
*
* @throws \Exception
* @return mixed
*/
function getLibrariesInfo() {
if (!function_exists('libraries_info')) {
// Libraries is at a lower version, which does not have this function.
return array();
}
# drupal_static_reset('libraries_info');
return libraries_info();
}
/**
* @see libraries_get_path()
*
* @param string $name
* Name of the library.
*
* @throws \Exception
* @return string|false
*/
function librariesGetPath($name) {
if (!function_exists('libraries_get_path')) {
throw new \Exception('Function libraries_get_path() does not exist.');
}
return libraries_get_path($name);
}
/**
* Called from xautoload_install() to set the module weight.
*
* @param int $weight
* New module weight for xautoload.
*/
public function installSetModuleWeight($weight) {
db_update('system')
->fields(array('weight' => $weight))
->condition('name', 'xautoload')
->condition('type', 'module')
->execute();
system_list_reset();
}
/**
* @param string $cid
* @param string $bin
*
* @return mixed
*
* @see cache_get()
*/
public function cacheGet($cid, $bin = 'cache') {
return cache_get($cid, $bin);
}
/**
* @param string $cid
* @param mixed $data
* @param string $bin
*
* @return mixed
*
* @see cache_set()
*/
public function cacheSet($cid, $data, $bin = 'cache') {
cache_set($cid, $data, $bin);
}
/**
* @param string|null $cid
* @param string|null $bin
*
* @see cache_clear_all()
*/
public function cacheClearAll($cid = NULL, $bin = NULL) {
cache_clear_all($cid, $bin);
}
/**
* @param string $key
*/
public function drupalStaticReset($key) {
\drupal_static_reset($key);
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Drupal\xautoload\DrupalSystem;
interface DrupalSystemInterface {
/**
* Wrapper for variable_set()
*
* @param string $name
* @param mixed $value
*/
function variableSet($name, $value);
/**
* Replacement of variable_get().
*
* @param string $name
* @param mixed $default
*
* @return mixed
*
* @see variable_get()
*/
function variableGet($name, $default = NULL);
/**
* Replacement of drupal_get_filename(), but returning an absolute file path.
*
* @param string $type
* @param string $name
*
* @return string
* The result of drupal_get_filename() with DRUPAL_ROOT . '/' prepended.
*
* @see drupal_get_filename()
*/
function drupalGetFilename($type, $name);
/**
* Replacement of drupal_get_path(), but returning an absolute directory path.
*
* @param string $type
* @param string $name
*
* @return string
*
* @see drupal_get_path()
*/
function drupalGetPath($type, $name);
/**
* @param string[] $extension_names
* Extension names.
*
* @return string[]
* Extension types by extension name.
*/
function getExtensionTypes($extension_names);
/**
* Gets active extensions directly from the system table.
*
* @return string[]
* Extension types by extension name.
*/
function getActiveExtensions();
/**
* Wrapper for module_list()
*
* @return array
*/
function moduleList();
/**
* Wrapper for module_implements()
*
* @param string $hook
*
* @return array[]
*/
function moduleImplements($hook);
/**
* @see libraries_info()
*
* @return mixed
*/
function getLibrariesInfo();
/**
* @see libraries_get_path()
*
* @param string $name
* Name of the library.
*
* @return string|false
*/
function librariesGetPath($name);
/**
* Called from xautoload_install() to set the module weight.
*
* @param int $weight
* New module weight for xautoload.
*/
public function installSetModuleWeight($weight);
/**
* @param string $cid
* @param string $bin
*
* @return object|false
* The cache or FALSE on failure.
*
* @see cache_get()
*/
public function cacheGet($cid, $bin = 'cache');
/**
* @param string $cid
* @param mixed $data
* @param string $bin
*
* @return mixed
*
* @see cache_set()
*/
public function cacheSet($cid, $data, $bin = 'cache');
/**
* @param string|null $cid
* @param string|null $bin
*
* @see cache_clear_all()
*/
public function cacheClearAll($cid = NULL, $bin = NULL);
/**
* @param string $key
*/
public function drupalStaticReset($key);
}

View File

@ -0,0 +1,127 @@
<?php
namespace Drupal\xautoload\Libraries;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\ClassFinder\InjectedApi\InjectedApiInterface;
use Drupal\xautoload\ClassFinder\Plugin\FinderPluginInterface;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\Phases\PhaseObserverInterface;
/**
* Registers autoload mappings from all libraries on hook_init(), or after the
* first cache miss.
*/
class LibrariesFinderPlugin implements FinderPluginInterface {
/**
* @var ExtendedClassFinderInterface
*/
private $finder;
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @param ExtendedClassFinderInterface $finder
* @param DrupalSystemInterface $system
*/
function __construct(ExtendedClassFinderInterface $finder, DrupalSystemInterface $system) {
$this->finder = $finder;
$this->system = $system;
}
/**
* Find the file for a class that in PSR-0 or PEAR would be in
* $psr_0_root . '/' . $path_fragment . $path_suffix
*
* @param InjectedApiInterface $api
* @param string $logical_base_path
* @param string $relative_path
*
* @return bool|null
* TRUE, if the file was found.
* FALSE or NULL, otherwise.
*/
function findFile($api, $logical_base_path, $relative_path) {
// Prevent recursion if this is called from libraries_info().
// @todo Find a better way to do this?
$backtrace = defined('DEBUG_BACKTRACE_IGNORE_ARGS')
? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
: debug_backtrace(FALSE);
foreach ($backtrace as $call) {
if ('libraries_info' === $call['function']) {
return FALSE;
}
}
$this->finder->getNamespaceMap()->unregisterDeepPath('', '');
$this->finder->getPrefixMap()->unregisterDeepPath('', '');
$this->registerAllLibraries();
return $this->finder->apiFindFile($api, $api->getClass());
}
/**
* Registers all libraries that have an "xautoload" setting.
*/
private function registerAllLibraries() {
$adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
foreach ($info = $this->getLibrariesXautoloadInfo() as $name => $pathAndCallback) {
list($path, $callback) = $pathAndCallback;
if (!is_callable($callback)) {
continue;
}
if (!is_dir($path)) {
continue;
}
$adapter->setExtensionDir($path);
call_user_func($callback, $adapter, $path);
}
}
/**
* @return array[]
*/
private function getLibrariesXautoloadInfo() {
$cached = $this->system->cacheGet(XAUTOLOAD_CACHENAME_LIBRARIES_INFO);
if (FALSE !== $cached) {
return $cached->data;
}
$info = $this->buildLibrariesXautoloadInfo();
$this->system->cacheSet(XAUTOLOAD_CACHENAME_LIBRARIES_INFO, $info);
return $info;
}
/**
* @return array[]
*/
private function buildLibrariesXautoloadInfo() {
// @todo Reset drupal_static('libraries') ?
$all = array();
foreach ($this->system->getLibrariesInfo() as $name => $info) {
if (!isset($info['xautoload'])) {
continue;
}
$callback = $info['xautoload'];
if (!is_callable($callback)) {
continue;
}
/** See https://www.drupal.org/node/2473901 */
$path = isset($info['library path'])
? $info['library path']
: $this->system->librariesGetPath($name);
if (FALSE === $path) {
continue;
}
$all[$name] = array($path, $callback);
}
return $all;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Drupal\xautoload\Libraries;
class LibrariesInfoAlter {
/**
* Empty constructor.
*
* Required for versions of PHP under 5.3.3, to prevent fallback to
* librariesInfoAlter() as the default constructor.
*/
function __construct() {
}
/**
* @param array $info
*
* @see hook_libraries_info_alter()
* @see xautoload_libraries_info_alter()
*/
function librariesInfoAlter(&$info) {
foreach ($info as $library_name => &$library_info) {
if (1
&& isset($library_info['xautoload'])
&& is_callable($library_info['xautoload'])
) {
$this->alterLibraryInfo($library_info, $library_name);
}
}
}
/**
* @param array $library_info
* @param string $library_name
*/
private function alterLibraryInfo(&$library_info, $library_name) {
$callable = $library_info['xautoload'];
if ($callable instanceof \Closure) {
// Wrap the closure so it can be serialized.
$callable = new SerializableClosureWrapper(
$library_info['xautoload'],
// The module name and library name allow the closure to be recovered on
// unserialize.
$library_info['module'],
$library_name);
$library_info['xautoload'] = $callable;
}
# $library_info['callbacks']['pre-load'][] = new LibrariesPreLoadCallback($callable);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Drupal\xautoload\Libraries;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\Phases\PhaseObserverInterface;
/**
* Registers autoload mappings from all libraries on hook_init(), or after the
* first cache miss.
*/
class LibrariesOnInit implements PhaseObserverInterface {
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @var ExtendedClassFinderInterface
*/
private $finder;
/**
* @param DrupalSystemInterface $system
*/
function __construct(DrupalSystemInterface $system) {
$this->system = $system;
}
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* @param string[] $extensions
* Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
$this->finder = $finder;
}
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase() {
// Nothing.
}
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase() {
$this->registerLibrariesFinderPlugin();
}
/**
* React to new extensions that were just enabled.
*
* @param string $name
* @param string $type
*/
public function welcomeNewExtension($name, $type) {
// Nothing.
}
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules) {
$this->system->drupalStaticReset('libraries_info');
$this->system->cacheClearAll(XAUTOLOAD_CACHENAME_LIBRARIES_INFO, 'cache');
$this->registerLibrariesFinderPlugin();
}
/**
* Registers all libraries that have an "xautoload" setting.
*/
private function registerLibrariesFinderPlugin() {
$plugin = new LibrariesFinderPlugin($this->finder, $this->system);
$this->finder->getPrefixMap()->registerDeepPath('', '', $plugin);
$this->finder->getNamespaceMap()->registerDeepPath('', '', $plugin);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Drupal\xautoload\Libraries;
class LibrariesPreLoadCallback {
/**
* @var callable
* A callable that is serializable, so not a closure.
* Can be a SerializableClosureWrapper.
*/
private $callable;
/**
* @param callable $callable
*/
function __construct($callable) {
$this->callable = $callable;
}
/**
* Callback that is applied directly before the library is loaded. At this
* point the library contains variant-specific information, if specified. Note
* that in this group the 'variants' property is no longer available.
*
* @param array $library
* An array of library information belonging to the top-level library, a
* specific version, a specific variant or a specific variant of a specific
* version. Because library information such as the 'files' property (see
* above) can be declared in all these different locations of the library
* array, but a callback may have to act on all these different parts of the
* library, it is called recursively for each library with a certain part of
* the libraries array passed as $library each time.
* @param string|null $version
* If the $library array belongs to a certain version (see above), a string
* containing the version. This argument may be empty, so NULL should be
* specified as the default value.
* @param string|null $variant
* If the $library array belongs to a certain variant (see above), a string
* containing the variant name. This argument may be empty, so NULL should
* be specified as the default value.
*/
function __invoke($library, $version, $variant) {
if (!empty($library['installed'])) {
xautoload()->proxyFinder->observeFirstCacheMiss(
new LibraryCacheMissObserver($this->callable, $library['library path']));
}
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Drupal\xautoload\Libraries;
use Drupal\xautoload\CacheMissObserver\CacheMissObserverInterface;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
class LibraryCacheMissObserver implements CacheMissObserverInterface {
/**
* @var callable
*/
private $callable;
/**
* @var string
*/
private $path;
/**
* @param callable $callable
* @param string $path
*/
function __construct($callable, $path) {
$this->callable = $callable;
$this->path = $path;
}
/**
* Executes the operation.
*
* This method will only be called if and when the "real" class finder is
* initialized.
*
* @param ExtendedClassFinderInterface $finder
* The class finder.
*/
function cacheMiss($finder) {
$adapter = \xautoload_InjectedAPI_hookXautoload::create($finder, $this->path);
call_user_func($this->callable, $adapter, $this->path);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Drupal\xautoload\Libraries;
/**
* A wrapper that allows serialization of closures from hook_libraries_info().
*/
class SerializableClosureWrapper {
/**
* The closure
*
* @var \Closure
*/
private $closure;
/**
* Module that implements hook_libraries_info()
*
* @var string
*/
private $moduleName;
/**
* Name of the library that has this closure for xautoload.
*
* @var string
*/
private $libraryName;
/**
* @param \Closure $closure
* @param string $moduleName
* @param string $libraryName
*/
public function __construct($closure, $moduleName, $libraryName) {
$this->closure = $closure;
$this->moduleName = $moduleName;
$this->libraryName = $libraryName;
}
public function __sleep() {
return array('moduleName', 'libraryName');
}
/**
* @param \Drupal\xautoload\Adapter\LocalDirectoryAdapter $adapter
*/
public function __invoke($adapter) {
$closure = $this->lazyGetClosure();
if ($closure instanceof \Closure) {
$closure($adapter);
}
}
/**
* @return \Closure|FALSE
*/
private function lazyGetClosure() {
return isset($this->closure)
? $this->closure
: $this->closure = $this->loadClosure();
}
/**
* @return \Closure|FALSE
*/
private function loadClosure() {
$source_function = $this->moduleName . '_libraries_info';
if (!function_exists($source_function)) {
return FALSE;
}
$module_libraries = $source_function();
if (!isset($module_libraries[$this->libraryName]['xautoload'])) {
return FALSE;
}
$closure_candidate = $module_libraries[$this->libraryName]['xautoload'];
if (!$closure_candidate instanceof \Closure) {
return FALSE;
}
return $closure_candidate;
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Drupal\xautoload;
use Drupal\xautoload\DIC\ServiceContainer;
use Drupal\xautoload\DIC\ServiceContainerInterface;
class Main implements ServiceContainerInterface {
/**
* @var ServiceContainer
* The service container, similar to a DIC.
*/
protected $services;
/**
* @param ServiceContainer $services
*/
function __construct($services) {
$this->services = $services;
}
/**
* @return ServiceContainer
*/
function getServiceContainer() {
return $this->services;
}
/**
* Invalidate all values stored in APC.
*/
function flushCache() {
$this->services->cacheManager->renewCachePrefix();
}
/**
* Register a module in early bootstrap, or from modulename.install.
*
* This is only needed for modules that need autoloading very early in the
* request, or e.g. during uninstall, or any situation that xautoload cannot
* catch up with.
*
* The method will register all autoloading schemes for this module that are
* supported by default:
* - PSR-0: "Drupal\\$module\\Foo" => "$module_dir/lib/Drupal/$module/Foo.php"
* - PEAR-FLAT: $module . "_Foo_Bar" => "$module_dir/lib/Foo/Bar.php"
*
* It will not register anything for PSR-4, since it is not clear whether this
* will be in "/lib/" or "/src/" or elsewhere.
*
* Suggested usage: (in your $modulename.module, or $modulename.install):
*
* xautoload()->registerModule(__FILE__);
*
* @param string $__FILE__
* File path to a *.module or *.install file.
* The basename of the file MUST be the module name.
* It is recommended to call this from the respective file itself, using the
* __FILE__ constant for this argument.
*/
function registerModule($__FILE__) {
$this->services->extensionNamespaces->registerExtension($__FILE__);
}
/**
* Register a module as PSR-4, in early bootstrap or from modulename.install
*
* This can be used while Drupal 8 is still undecided whether PSR-4 class
* files should live in "lib" or in "src" by default.
*
* Suggested usage: (in your $modulename.module, or $modulename.install):
*
* // E.g. "Drupal\\$module\\Foo" => "$module_dir/lib/Foo.php"
* xautoload()->registerModulePsr4(__FILE__, 'lib');
*
* or
*
* // E.g. "Drupal\\$module\\Foo" => "$module_dir/src/Foo.php"
* xautoload()->registerModulePsr4(__FILE__, 'src');
*
* or
*
* // E.g. "Drupal\\$module\\Foo" => "$module_dir/psr4/Foo.php"
* xautoload()->registerModulePsr4(__FILE__, 'psr4');
*
* @param string $__FILE__
* File path to a *.module or *.install file.
* The basename of the file MUST be the module name.
* It is recommended to call this from the respective file itself, using the
* __FILE__ constant for this argument.
* @param string $subdir
* The PSR-4 base directory for the module namespace, relative to the module
* directory. E.g. "src" or "lib".
*/
function registerModulePsr4($__FILE__, $subdir) {
$this->services->extensionNamespaces->registerExtensionPsr4($__FILE__, $subdir);
}
/**
* Magic getter for service objects. This lets this class act as a proxy for
* the service container.
*
* @param string $key
* @return mixed
*/
function __get($key) {
return $this->services->__get($key);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\ClassFinder\Plugin\DrupalCoreRegistryPlugin;
class DrupalCoreRegistryRegistrator implements PhaseObserverInterface {
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* The class finder object, with any cache decorator peeled off.
* @param string[] $extensions
* Currently enabled extensions. Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
$plugin = new DrupalCoreRegistryPlugin(DRUPAL_ROOT . '/');
$finder->getNamespaceMap()->registerDeepPath('', 'registry', $plugin);
$finder->getPrefixMap()->registerDeepPath('', 'registry', $plugin);
}
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase() {
// Nothing.
}
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase() {
// Nothing.
}
/**
* React to new extensions that were just enabled.
*
* @param string $name
* @param string $type
*/
public function welcomeNewExtension($name, $type) {
// Nothing.
}
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules) {
// Nothing.
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
use Drupal\xautoload\CacheMissObserver\CacheMissObserverInterface;
/**
* Records events during a Drupal request, and forwards them to the registered
* observers after the first class loader cache miss.
*/
class DrupalPhaseControl implements CacheMissObserverInterface {
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @var PhaseObserverInterface[]
*/
private $observers;
/**
* @var bool
* TRUE, if the class finder is no longer the cached one..
*/
private $awake = FALSE;
/**
* @var string[]|null
* Extension type by extension name.
*/
private $extensions;
/**
* @var bool
* TRUE, if in main phase.
*/
private $mainPhase = FALSE;
/**
* @var bool
* TRUE, if in of after boot phase.
*/
private $bootPhase;
/**
* @param DrupalSystemInterface $system
* @param PhaseObserverInterface[] $observers
*/
public function __construct(DrupalSystemInterface $system, array $observers) {
$this->system = $system;
$this->observers = $observers;
}
/**
* {@inheritdoc}
*/
public function cacheMiss($finder) {
$this->extensions = $this->system->getActiveExtensions();
foreach ($this->observers as $observer) {
$observer->wakeUp($finder, $this->extensions);
}
$this->awake = TRUE;
if ($this->bootPhase) {
// We slipped into boot phase while asleep. Need to catch up.
foreach ($this->observers as $observer) {
$observer->enterBootPhase();
}
}
if ($this->mainPhase) {
// We slipped into main phase while asleep. Need to catch up.
foreach ($this->observers as $observer) {
$observer->enterMainPhase();
}
}
}
public function enterBootPhase() {
if ($this->bootPhase) {
// We are already in the main phase. Nothing changes.
return;
}
$this->bootPhase = TRUE;
if (!$this->awake) {
// The entire thing is not initialized yet.
// Postpone until operateOnFinder()
return;
}
foreach ($this->observers as $observer) {
$observer->enterBootPhase();
}
}
/**
* Initiate the main phase.
*
* Called from
* @see xautoload_custom_theme()
* @see xautolaod_init()
*/
public function enterMainPhase() {
// Main phase implies boot phase.
$this->enterBootPhase();
if ($this->mainPhase) {
// We are already in the main phase. Nothing changes.
return;
}
$this->mainPhase = TRUE;
if (!$this->awake) {
// The entire thing is not initialized yet.
// Postpone until operateOnFinder()
return;
}
foreach ($this->observers as $observer) {
$observer->enterMainPhase();
}
}
/**
* Checks if new extensions have been enabled, and registers them.
*
* This is called from xautoload_module_implements_alter(), which is called
* whenever a new module is enabled, but also some calls we need to ignore.
*/
public function checkNewExtensions() {
if (!$this->awake) {
// The entire thing is not initialized yet.
// Postpone until operateOnFinder()
return;
}
$activeExtensions = $this->system->getActiveExtensions();
if ($activeExtensions === $this->extensions) {
// Nothing actually changed. False alarm.
return;
}
// Now check all extensions to find out if any of them is new.
foreach ($activeExtensions as $name => $type) {
if (!isset($this->extensions[$name])) {
// This extension was freshly enabled.
if ('xautoload' === $name) {
// If xautoload is enabled in this request, then boot phase and main
// phase are not properly initialized yet.
$this->enterMainPhase();
}
// Notify observers about the new extension.
foreach ($this->observers as $observer) {
$observer->welcomeNewExtension($name, $type);
}
}
}
}
/**
* Called from @see xautoload_modules_enabled()
*
* @param $modules
*/
public function modulesEnabled($modules) {
if (!$this->awake) {
// No need to postpone.
// initMainPhase() will have these modules included.
return;
}
foreach ($this->observers as $observer) {
$observer->modulesEnabled($modules);
}
}
}

View File

@ -0,0 +1,253 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\ClassFinder\Plugin\DrupalExtensionNamespaceFinderPlugin;
use Drupal\xautoload\ClassFinder\Plugin\DrupalExtensionUnderscoreFinderPlugin;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
class ExtensionNamespaces implements PhaseObserverInterface {
/**
* @var ExtendedClassFinderInterface
*/
private $finder;
/**
* @var (string|false)[]
*/
private $queue = array();
/**
* Which modules are already registered.
*
* @var bool[]
*/
private $registered = array();
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @param DrupalSystemInterface $system
*/
public function __construct(DrupalSystemInterface $system) {
$this->system = $system;
}
/**
* Registers the namespaces for a module even though it might be currently
* disabled, or even though it might be early in the request.
*
* @param string $__FILE__
*/
public function registerExtension($__FILE__) {
if (NULL === $this->finder) {
// Sleeping..
$this->queue[$__FILE__] = FALSE;
return;
}
$info = pathinfo($__FILE__);
$name = $info['filename'];
// Check if something was registered before.
if (isset($this->registered[$name])) {
// Already registered.
return;
}
$this->_registerExtension($name, $info['dirname']);
}
/**
* Registers the namespace with PSR-4 for a module even though it might be
* currently disabled, or even though it might be early in the request.
*
* @param string $__FILE__
* @param string $subdir
*/
public function registerExtensionPsr4($__FILE__, $subdir) {
if (NULL === $this->finder) {
// Sleeping..
$this->queue[$__FILE__] = $subdir;
return;
}
$info = pathinfo($__FILE__);
$name = $info['filename'];
// Check if something was registered before.
if (isset($this->registered[$name])) {
if ('psr-4' === $this->registered[$name]) {
// It already happened.
return;
}
// Unregister the lazy plugins.
$this->finder->getNamespaceMap()->unregisterDeepPath(
'Drupal/' . $name . '/',
$name
);
$this->finder->getPrefixMap()->unregisterDeepPath(
str_replace('_', '/', $name) . '/',
$name
);
}
$this->_registerExtensionPsr4($name, $info['dirname'], $subdir);
}
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* @param string[] $extensions
* Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
$this->finder = $finder;
// Register queued extensions.
foreach ($this->queue as $__FILE__ => $subdir) {
$info = pathinfo($__FILE__);
$name = $info['filename'];
$dir = $info['dirname'];
if (FALSE === $subdir) {
// This is not PSR-4.
$this->_registerExtension($name, $dir);
}
else {
// This is PSR-4.
$this->_registerExtensionPsr4($name, $dir, $subdir);
}
}
$extensions = array_diff_key($extensions, $this->registered);
// Register remaining extensions, using the lazy plugins.
$this->_registerLazyExtensionPlugins($extensions);
}
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase() {
// Nothing.
}
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase() {
// Nothing.
}
/**
* React to new extensions that were just enabled.
*
* @param string $name
* @param string $type
*/
public function welcomeNewExtension($name, $type) {
if (isset($this->registered[$name])) {
// Already registered.
return;
}
$dir = $this->system->drupalGetPath($type, $name);
$this->_registerExtension($name, $dir);
}
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules) {
// Nothing.
}
/**
* @param string $name
* @param string $dir
*/
private function _registerExtension($name, $dir) {
if (is_dir($lib = $dir . '/lib')) {
$this->finder->addPsr0('Drupal\\' . $name . '\\', $lib);
$this->finder->addPearFlat($name . '_', $lib);
}
if (is_dir($src = $dir . '/src')) {
$this->finder->addPsr4('Drupal\\' . $name . '\\', $src);
}
$this->registered[$name] = TRUE;
}
/**
* @param string $name
* @param string $dir
* @param string $subdir
*/
private function _registerExtensionPsr4($name, $dir, $subdir) {
$this->finder->addPsr4('Drupal\\' . $name . '\\', $dir . '/' . $subdir);
// Re-add the PSR-0 test directory, for consistency's sake.
if (is_dir($lib_tests = $dir . '/lib/Drupal/' . $name . '/Tests')) {
$this->finder->addPsr0('Drupal\\' . $name . '\\Tests\\', $lib_tests);
}
$this->registered[$name] = 'psr-4';
}
/**
* Register lazy plugins for enabled Drupal modules and themes, assuming that
* we don't know yet whether they use PSR-0, PEAR-Flat, or none of these.
*
* @param string[] $extensions
* An array where the keys are extension names, and the values are extension
* types like 'module' or 'theme'.
*/
private function _registerLazyExtensionPlugins(array $extensions) {
$namespaceBehaviors = array();
$prefixBehaviors = array();
foreach (array('module', 'theme') as $extension_type) {
$namespaceBehaviors[$extension_type] = new DrupalExtensionNamespaceFinderPlugin(
$extension_type,
$this->finder->getNamespaceMap(),
$this->finder->getPrefixMap(),
$this->system);
$prefixBehaviors[$extension_type] = new DrupalExtensionUnderscoreFinderPlugin(
$extension_type,
$this->finder->getNamespaceMap(),
$this->finder->getPrefixMap(),
$this->system);
}
$prefix_map = array();
$namespace_map = array();
foreach ($extensions as $name => $type) {
if (empty($namespaceBehaviors[$type])) {
// Unsupported extension type, e.g. "theme_engine".
// This can happen if a site was upgraded from Drupal 6.
// See https://drupal.org/comment/8503979#comment-8503979
continue;
}
if (!empty($this->registered[$name])) {
// The extension has already been processed.
continue;
}
$namespace_map['Drupal/' . $name . '/'][$name] = $namespaceBehaviors[$type];
$prefix_map[str_replace('_', '/', $name) . '/'][$name] = $prefixBehaviors[$type];
$this->registered[$name] = TRUE;
}
$this->finder->getNamespaceMap()->registerDeepPaths($namespace_map);
$this->finder->getPrefixMap()->registerDeepPaths($prefix_map);
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
/**
* A variation of hook_xautoload() that fires
* - when all *module file are included:
* - xautoload_custom_theme(), or
* - xautoload_init(), whichever is first.
* - when module_enable() has finished:
* - xautoload_modules_enabled()
*
* This can cause any implementation to be fired multiple times per request.
*/
class HookXautoload implements PhaseObserverInterface {
/**
* @var ExtendedClassFinderInterface|null
*/
private $finder = NULL;
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @var string[]
*/
private $extensions;
/**
* @param DrupalSystemInterface $system
*/
public function __construct(DrupalSystemInterface $system) {
$this->system = $system;
}
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* @param string[] $extensions
* Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
$this->finder = $finder;
$this->extensions = $extensions;
}
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase() {
// Nothing.
}
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase() {
$modules = $this->system->moduleImplements('xautoload');
$this->runHookXautoload($modules);
}
/**
* New extensions were enabled/installed.
*
* @param string $name
* Extension type by name.
* @param string $type
*/
public function welcomeNewExtension($name, $type) {
// Nothing.
}
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules) {
$modules = $this->system->moduleImplements('xautoload');
$this->runHookXautoload($modules);
}
/**
* Runs hook_xautoload() on all enabled modules.
*
* This may occur multiple times in a request, if new modules are enabled.
*
* @param array $modules
*/
private function runHookXautoload(array $modules) {
// Let other modules register stuff to the finder via hook_xautoload().
$adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
foreach ($modules as $module) {
$adapter->setExtensionDir($dir = $this->system->drupalGetPath('module', $module));
$function = $module . '_xautoload';
$function($adapter, $dir);
}
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
use Drupal\xautoload\DrupalSystem\DrupalSystemInterface;
/**
* A variation of hook_xautoload() that fires very early, as soon as a *.module
* file is included, but only once per module / request.
*/
class HookXautoloadEarly implements PhaseObserverInterface {
/**
* @var ExtendedClassFinderInterface|null
*/
private $finder = NULL;
/**
* @var DrupalSystemInterface
*/
private $system;
/**
* @var string[]
*/
private $extensions;
/**
* @param DrupalSystemInterface $system
*/
public function __construct(DrupalSystemInterface $system) {
$this->system = $system;
}
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* @param string[] $extensions
* Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions) {
$this->finder = $finder;
$this->extensions = $extensions;
}
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase() {
// @todo Call hook_xautoload() on bootstrap modules, if in bootstrap phase.
}
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase() {
// @todo Don't use moduleImplements(), to prevent hook_module_implements_alter()
$modules = $this->system->moduleImplements('xautoload');
// @todo Remove boot modules from the list.
$this->runHookXautoload($modules);
}
/**
* New extensions were enabled/installed.
*
* @param string $name
* Extension type by name.
* @param string $type
*/
public function welcomeNewExtension($name, $type) {
$function = $name . '_xautoload';
if (!function_exists($function)) {
return;
}
$dir = $this->system->drupalGetPath($type, $name);
$adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, $dir);
$function($adapter, $dir);
}
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules) {
// Nothing.
}
/**
* Runs hook_xautoload() on all enabled modules.
*
* This may occur multiple times in a request, if new modules are enabled.
*
* @param array $modules
*/
private function runHookXautoload(array $modules) {
// Let other modules register stuff to the finder via hook_xautoload().
$adapter = \xautoload_InjectedAPI_hookXautoload::create($this->finder, '');
foreach ($modules as $module) {
$adapter->setExtensionDir($dir = $this->system->drupalGetPath('module', $module));
$function = $module . '_xautoload';
$function($adapter, $dir);
}
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Drupal\xautoload\Phases;
use Drupal\xautoload\ClassFinder\ExtendedClassFinderInterface;
/**
* Observes switching into specific "phases" of a Drupal process.
*
* @see \Drupal\xautoload\Phases\DrupalPhaseControl
*
* These phases only fire after the class loader had a cache fail.
* This way, all the initialization logic, subscribing module namespaces etc,
* will not be executed on an average request.
*
* Phase switching events can hit the observer later in the request, even if the
* respective phase has already started long ago. E.g. the enterBootPhase() will
* still fire even if the cache miss happens during the "main phase".
*
* However, the order of phase events being called on an observer will always be
* the same. And wakeUp() will always be called before any other phase event,
* giving the observer the chance to set up the finder object.
*/
interface PhaseObserverInterface {
/**
* Wake up after a cache fail.
*
* @param ExtendedClassFinderInterface $finder
* The class finder object, with any cache decorator peeled off.
* @param string[] $extensions
* Currently enabled extensions. Extension type by extension name.
*/
public function wakeUp(ExtendedClassFinderInterface $finder, array $extensions);
/**
* Enter the boot phase of the request, where all bootstrap module files are included.
*/
public function enterBootPhase();
/**
* Enter the main phase of the request, where all module files are included.
*/
public function enterMainPhase();
/**
* React to new extensions that were just enabled.
*
* @param string $name
* @param string $type
*/
public function welcomeNewExtension($name, $type);
/**
* React to xautoload_modules_enabled()
*
* @param string[] $modules
* New module names.
*/
public function modulesEnabled($modules);
}

View File

@ -0,0 +1,202 @@
<?php
namespace Drupal\xautoload;
/**
* A number of static methods that don't interact with any global state.
*/
class Util {
/**
* Generate a random string made of uppercase and lowercase characters and numbers.
*
* @param int $length
* Length of the random string to generate
* @param string $chars
* Allowed characters
* @param string $chars_first
* Allowed characters for the first character.
*
* @return string
* Random string of the specified length
*/
static function randomString($length = 30, $chars = NULL, $chars_first = NULL) {
if (!isset($chars)) {
$chars = 'abcdefghijklmnopqrstuvwxyz' .
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .
'1234567890';
}
if (!isset($chars_first)) {
$chars_first = $chars;
}
// Initialize the randomizer.
srand((double) microtime() * 1000000);
$str = substr($chars_first, rand() % strlen($chars_first), 1);
for ($i = 0; $i < $length; ++$i) {
$str .= substr($chars, rand() % strlen($chars), 1);
}
return $str;
}
/**
* Generate a random string that is a valid PHP identifier.
*
* @param int $length
* Length of the random string to generate
*
* @return string
* Random string of the specified length
*/
static function randomIdentifier($length = 40) {
// Since PHP is case insensitive, we only user lowercase characters.
$chars_first = 'abcdefghijklmnopqrstuvwxyz_';
$chars = 'abcdefghijklmnopqrstuvwxyz_1234567890';
return self::randomString($length, $chars, $chars_first);
}
/**
* Generate a random string that is a stream wrapper protocol.
*
* @param int $length
* Length of the random string to generate
*
* @return string
* Random string of the specified length
*/
static function randomProtocol($length = 40) {
// Since PHP is case insensitive, we only user lowercase characters.
$chars_first = 'abcdefghijklmnopqrstuvwxyz_';
$chars = 'abcdefghijklmnopqrstuvwxyz_1234567890';
return self::randomString($length, $chars, $chars_first);
}
/**
* Get a string representation of a callback for debug purposes.
*
* @param callback $callback
* A PHP callback, which could be an array or a string.
*
* @return string
* A string representation to be displayed to a user, e.g.
* "Foo::staticMethod()", or "Foo->bar()"
*/
static function callbackToString($callback) {
if (is_array($callback)) {
list($obj, $method) = $callback;
if (is_object($obj)) {
$str = get_class($obj) . '->' . $method . '()';
}
else {
$str = $obj . '::';
$str .= $method . '()';
}
}
else {
$str = $callback;
}
return $str;
}
/**
* Convert the underscores of a prefix into directory separators.
*
* @param string $prefix
* Prefix, without trailing underscore.
*
* @return string
* Path fragment representing the prefix, with trailing '/'.
*/
static function prefixLogicalPath($prefix) {
if (!strlen($prefix)) {
return '';
}
$pear_logical_path = str_replace('_', '/', rtrim($prefix, '_') . '_');
// Clean up surplus '/' resulting from duplicate underscores, or an
// underscore at the beginning of the class.
while (FALSE !== $pos = strrpos('/' . $pear_logical_path, '//')) {
$pear_logical_path[$pos] = '_';
}
return $pear_logical_path;
}
/**
* Replace the namespace separator with directory separator.
*
* @param string $namespace
* Namespace without trailing namespace separator.
*
* @return string
* Path fragment representing the namespace, with trailing '/'.
*/
static function namespaceLogicalPath($namespace) {
return
strlen($namespace)
? str_replace('\\', '/', rtrim($namespace, '\\') . '\\')
: '';
}
/**
* Check if a file exists, considering the full include path.
* Return the resolved path to that file.
*
* @param string $file
* The filepath
* @return boolean|string
* The resolved file path, if the file exists in the include path.
* FALSE, otherwise.
*/
static function findFileInIncludePath($file) {
if (function_exists('stream_resolve_include_path')) {
// Use the PHP 5.3.1+ way of doing this.
return stream_resolve_include_path($file);
}
elseif ($file[0] === '/') {
// That's an absolute path already.
return file_exists($file)
? $file
: FALSE;
}
else {
// Manually loop all candidate paths.
foreach (explode(PATH_SEPARATOR, get_include_path()) as $base_dir) {
if (file_exists($base_dir . '/' . $file)) {
return $base_dir . '/' . $file;
}
}
return FALSE;
}
}
/**
* Checks whether an identifier is defined as either a class, interface or
* trait. Does not trigger autoloading.
*
* @param string $class
* @return bool
*/
static function classIsDefined($class) {
return class_exists($class, FALSE)
|| interface_exists($class, FALSE)
|| (PHP_VERSION_ID >= 50400 && trait_exists($class, FALSE));
}
/**
* Dummy method to force autoloading this class (or an ancestor).
*/
static function forceAutoload() {
// Do nothing.
}
}

View File

@ -0,0 +1,37 @@
<?php
use Drupal\xautoload\Discovery\ClassMapGenerator;
require_once dirname(__DIR__) . '/xautoload.early.lib.inc';
_xautoload_register();
xautoload()->finder->addPsr4('Drupal\xautoload\Tests\\', __DIR__ . '/src/');
// Use a non-cached class map generator.
xautoload()->getServiceContainer()->set('classMapGenerator', new ClassMapGenerator());
// Register a one-off class loader for the test PSR-4 classes.
/*
call_user_func(
function() {
$addPsr4 = function($namespace, $src) {
$strlen = strlen($namespace);
spl_autoload_register(
function ($class) use ($src, $namespace, $strlen) {
if (0 === strpos($class, $namespace)) {
$file = $src . '/' . str_replace('\\', '/', substr($class, $strlen)) . '.php';
if (file_exists($file)) {
require_once $file;
return TRUE;
}
}
return FALSE;
}
);
};
$addPsr4('Drupal\xautoload\Tests\\', __DIR__ . '/src');
$addPsr4('Drupal\xautoload\\', dirname(__DIR__) . '/src');
}
);
*/

View File

@ -0,0 +1,8 @@
<?php
namespace Acme\ComposerTargetDirTestLib;
class Foo {
}

View File

@ -0,0 +1,8 @@
{
"autoload": {
"psr-0": {
"Acme\\ComposerTargetDirTestLib\\": ""
}
},
"target-dir": "Acme/ComposerTargetDirTestLib/"
}

View File

@ -0,0 +1,8 @@
{
"autoload": {
"psr-4": {
"ComposerTestLib\\": "psr4"
},
"classmap": ["other"]
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace ComposerTestLib\Other;
class Foo {
}

View File

@ -0,0 +1,9 @@
<?php
namespace ComposerTestLib;
class Foo {
}

View File

@ -0,0 +1,5 @@
<?php
namespace Acme\TestLib;
class Foo {}

View File

@ -0,0 +1,12 @@
<?php
use Drupal\xautoload\Tests\VirtualDrupal\DrupalEnvironment;
/**
* @param string $name
*
* @see libraries_load()
*/
function libraries_load($name) {
DrupalEnvironment::getInstance()->librariesLoad($name);
}

View File

@ -0,0 +1 @@
<?php

View File

@ -0,0 +1,5 @@
<?php
namespace Drupal\testmod;
class Foo {}

View File

@ -0,0 +1,16 @@
<?php
function testmod_enable() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
# new \Drupal\testmod\Foo();
}
function testmod_install() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
# new \Drupal\testmod\Foo();
}
function testmod_schema() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
# new \Drupal\testmod\Foo();
}

View File

@ -0,0 +1,91 @@
<?php
use Drupal\xautoload\Adapter\LocalDirectoryAdapter;
use Drupal\xautoload\Tests\Util\StaticCallLog;
/**
* Implements hook_init()
*/
function testmod_init() {
StaticCallLog::addCall();
new \Drupal\testmod\Foo();
# libraries_load('testlib');
new \Acme\TestLib\Foo();
new \ComposerTestLib\Other\Foo();
new \ComposerTestLib\Foo();
new \Acme\ComposerTargetDirTestLib\Foo();
}
/**
* Implements hook_modules_enabled()
*/
function testmod_modules_enabled() {
StaticCallLog::addCall();
# new \Drupal\testmod\Foo();
new \Acme\TestLib\Foo();
new \ComposerTestLib\Other\Foo();
new \ComposerTestLib\Foo();
new \Acme\ComposerTargetDirTestLib\Foo();
}
/**
* Implements hook_watchdog()
*/
function testmod_watchdog() {
StaticCallLog::addCall();
# new \Drupal\testmod\Foo();
# new \Acme\TestLib\Foo();
}
/**
* Implements hook_xautoload()
*
* @param LocalDirectoryAdapter $adapter
*/
function testmod_xautoload($adapter) {
StaticCallLog::addCall();
$adapter->addPsr4('Drupal\testmod\\', 'psr4');
new \Drupal\testmod\Foo();
}
/**
* Implements hook_libraries_info()
*/
function testmod_libraries_info() {
StaticCallLog::addCall();
new \Drupal\testmod\Foo();
return array(
'testlib' => array(
'name' => 'Test library',
'xautoload' => '_testmod_libraries_testlib_xautoload',
),
'ComposerTestLib' => array(
'xautoload' => '_testmod_libraries_ComposerTestLib_xautoload',
),
'ComposerTargetDirTestLib' => array(
'xautoload' => '_testmod_libraries_ComposerTargetDirTestLib_xautoload',
),
);
}
/**
* @param LocalDirectoryAdapter $adapter
*/
function _testmod_libraries_testlib_xautoload($adapter) {
StaticCallLog::addCall();
$adapter->addPsr4('Acme\TestLib\\', 'src');
}
/**
* @param LocalDirectoryAdapter $adapter
*/
function _testmod_libraries_ComposerTestLib_xautoload($adapter) {
$adapter->composerJson('composer.json');
}
/**
* @param LocalDirectoryAdapter $adapter
*/
function _testmod_libraries_ComposerTargetDirTestLib_xautoload($adapter) {
$adapter->composerJson('composer.json');
}

View File

@ -0,0 +1,3 @@
<?php
class testmod_pearflat_Foo {}

View File

@ -0,0 +1,15 @@
<?php
function testmod_pearflat_enable() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
new testmod_pearflat_Foo;
}
function testmod_pearflat_install() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
new testmod_pearflat_Foo;
}
function testmod_pearflat_schema() {
\Drupal\xautoload\Tests\Util\StaticCallLog::addCall();
new testmod_pearflat_Foo;
}

View File

@ -0,0 +1,18 @@
<?php
use Drupal\xautoload\Tests\Util\StaticCallLog;
function testmod_pearflat_init() {
StaticCallLog::addCall();
new testmod_pearflat_Foo;
}
function testmod_pearflat_modules_enabled() {
StaticCallLog::addCall();
new testmod_pearflat_Foo;
}
function testmod_pearflat_watchdog() {
StaticCallLog::addCall();
new testmod_pearflat_Foo;
}

View File

@ -0,0 +1,5 @@
<?php
namespace Drupal\testmod_psr0_lib;
class Foo {}

View File

@ -0,0 +1,19 @@
<?php
use Drupal\testmod_psr0_lib\Foo;
use Drupal\xautoload\Tests\Util\StaticCallLog;
function testmod_psr0_lib_enable() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr0_lib_install() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr0_lib_schema() {
StaticCallLog::addCall();
new Foo;
}

View File

@ -0,0 +1,19 @@
<?php
use Drupal\testmod_psr0_lib\Foo;
use Drupal\xautoload\Tests\Util\StaticCallLog;
function testmod_psr0_lib_init() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr0_lib_modules_enabled() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr0_lib_watchdog() {
StaticCallLog::addCall();
new Foo;
}

View File

@ -0,0 +1,5 @@
<?php
namespace Drupal\testmod_psr4_custom;
class Foo {}

View File

@ -0,0 +1,19 @@
<?php
use Drupal\testmod_psr4_custom\Foo;
use Drupal\xautoload\Tests\Util\StaticCallLog;
function testmod_psr4_custom_enable() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_custom_install() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_custom_schema() {
StaticCallLog::addCall();
new Foo;
}

View File

@ -0,0 +1,21 @@
<?php
use Drupal\testmod_psr4_custom\Foo;
use Drupal\xautoload\Tests\Util\StaticCallLog;
xautoload()->main->registerModulePsr4(__FILE__, 'psr4');
function testmod_psr4_custom_init() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_custom_modules_enabled() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_custom_watchdog() {
StaticCallLog::addCall();
new Foo;
}

View File

@ -0,0 +1,5 @@
<?php
namespace Drupal\testmod_psr4_src;
class Foo {}

View File

@ -0,0 +1,19 @@
<?php
use Drupal\testmod_psr4_src\Foo;
use Drupal\xautoload\Tests\Util\StaticCallLog;
function testmod_psr4_src_enable() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_src_install() {
StaticCallLog::addCall();
new Foo;
}
function testmod_psr4_src_schema() {
StaticCallLog::addCall();
new Foo;
}

Some files were not shown because too many files have changed in this diff Show More