[ '{:library}\extensions\adapter\{:namespace}\{:class}\{:name}', '{:library}\{:namespace}\{:class}\adapter\{:name}' ], 'command' => [ '{:library}\extensions\command\{:namespace}\{:class}\{:name}', '{:library}\console\command\{:namespace}\{:class}\{:name}' ], 'controllers' => [ '{:library}\controllers\{:namespace}\{:class}\{:name}Controller' ], 'data' => [ '{:library}\extensions\data\{:namespace}\{:class}\{:name}', '{:library}\data\{:namespace}\{:class}\adapter\{:name}', '{:library}\data\{:namespace}\{:class}\{:name}', '{:library}\data\{:class}\adapter\{:name}' ], 'helper' => [ '{:library}\extensions\helper\{:name}', '{:library}\template\helper\{:name}' ], 'libraries' => [ '{:app}/libraries/{:name}', '{:root}/{:name}' ], 'models' => [ '{:library}\models\{:name}' ], 'strategy' => [ '{:library}\extensions\strategy\{:namespace}\{:class}\{:name}', '{:library}\extensions\strategy\{:class}\{:name}', '{:library}\{:namespace}\{:class}\strategy\{:name}' ], 'socket' => [ '{:library}\extensions\net\socket\{:name}', '{:library}\extensions\socket\{:name}', '{:library}\net\socket\{:name}' ], 'test' => [ '{:library}\extensions\test\{:namespace}\{:class}\{:name}', '{:library}\test\{:namespace}\{:class}\{:name}' ], 'tests' => [ '{:library}\tests\{:namespace}\{:class}\{:name}Test' ] ]; /** * Stores the name of the default library. When adding a library configuration to the * application, if the `'default'` option flag is set to `true`, the name of the library will * be assigned. To retrieve the default library's configuration, use `Libraries::get(true)`. * * @see lithium\core\Libraries::add() * @see lithium\core\Libraries::get() * @var string */ protected static $_default; /** * Holds cached class paths generated and used by `lithium\core\Libraries::load()`. * * @var array * @see lithium\core\Libraries::load() */ protected static $_cachedPaths = []; /** * Holds associations between fully-namespaced class names and file's paths mapped * with `lithium\core\Libraries::map()`. * * @var array * @see lithium\core\Libraries::map() * @see lithium\core\Libraries::unmap() */ protected static $_map = []; /** * Accessor method for the class path templates which `Libraries` uses to look up and load * classes. Using this method, you can define your own types of classes, or modify the default * organization of built-in class types. * * For example, in a queuing application, you can define a class type called `'job'`: * ``` * Libraries::paths(['job' => '{:library}\extensions\job\{:name}']); * ``` * * Then, any classes you add to the `extensions/job` directory in your application will be * automatically detected when calling `Libraries::locate('job')`. Additionally, any matching * classes in the `extensions/job` directory of any plugin or vendor library you add to your * application will also be detected. * * Supposing you wanted to have the option of further organizing jobs by class type (some jobs * are related to updating caches, others to sending notifications, etc.), you can specify * multiple paths per class type, with varying levels of specificity: * ``` * Libraries::paths(['job' => [ * '{:library}\extensions\job\{:class}\{:name}', * '{:library}\extensions\job\{:name}' * ]]); * ``` * * This allows you to, for example, have two different classes called `Cleanup`. One may be * located in `app\extensions\job\Cleanup`, while the other is in * `app\extensions\job\cache\Cleanup`. Calling: `Libraries::locate('job');` will find * both classes, while `Libraries::locate('job.cache');` will only find the second. You can * also find individual jobs by name: `Libraries::locate('job', 'Cleanup');` * * See `Libraries::locate()` for more information on using built-in and user-defined paths to * look up classes. * * In addition to adding custom class types, `paths()` allows you to redefine the naming and * organization of existing types. For example, if you wished to reference your model classes * as `app\models\PostModel` instead of `app\models\Post`, you can do the following: * ``` * Libraries::paths(['models' => '{:library}\models\{:name}Model']); * ``` * * Note, however, that this is a destructive, not an additive operation, and will * replace any existing paths defined for that type. If you wish to add a search path * for an existing type, you must do the following: * ``` * $existing = Libraries::paths('controllers'); * Libraries::paths(['controller' => array_merge( * ['{:library}\extensions\controllers\{:name}Controller'], (array) $existing * )]); * ``` * * @see lithium\core\Libraries::locate() * @see lithium\core\Libraries::$_paths * @param mixed $path If `$path` is a string, returns the path(s) associated with that path * type, or `null` if no paths are defined for that type. * @return mixed */ public static function paths($path = null) { if (empty($path)) { return static::$_paths; } if (is_string($path)) { return isset(static::$_paths[$path]) ? static::$_paths[$path] : null; } static::$_paths = array_filter(array_merge(static::$_paths, (array) $path)); } /** * Adds a class library from which files can be loaded. * * The `add()` method registers a named library configuration to your application, and is used * to allow the framework to auto-load classes on an as-needed basis. * * ### Adding libraries to your application * * In Lithium, libraries represent the broadest unit of class organization in an application, * and _everything_ is a library; this includes your application, and the Lithium framework * itself. Libraries can also be other frameworks, like Solar, Zend Framework or PEAR, or * Lithium plugins, which are simply libraries that follow the same organizational standards * as Lithium applications. * * By convention, libraries are placed in the `libraries` directory inside your application, or * the root `libraries` directory at the top level of the default distribution (i.e. the one * that contains the `lithium` directory), however, you can change this on a case-by-case basis * using the `'path'` key to specify an absolute path to the library's directory. * * @param string $name Library name, i.e. `'app'`, `'lithium'`, `'pear'` or `'aura'`. * @param array $config Specifies where the library is in the filesystem, and how classes * should be loaded from it. Allowed keys are: * - `'bootstrap'` _mixed_: A file path (relative to `'path'`) to a bootstrap script that * should be run when the library is added, or `true` to use the default bootstrap * path, i.e. `config/bootstrap.php`. * - `'defer'` _boolean_: If `true`, indicates that, when locating classes, this library * should defer to other libraries in order of preference. * - `'includePath'` _mixed_: If `true`, appends the absolutely-resolved value of * `'path'` to the PHP include path. If a string, the value is appended to PHP's. * - `'loader'`: An auto-loader method associated with the library, if any. * - `'path'`: The directory containing the library. * - `'prefix'` _string_: The class prefix this library uses, i.e. `'lithium\'`, * `'Zend_'` or `'Solar_'`. If the library has no global prefix, set to `false`. * - `'suffix'` _string_: Gets appended to the end of the file name. For example, most * libraries end classes in `'.php'`, but some use `'.class.php'`, or `'.inc.php'`. * - `'transform'` _\Closure_: Defines a custom way to transform a class name into its * corresponding file path. Accepts either an array of two strings which are * interpreted as the pattern and replacement for a regex, or an anonymous function, * which receives the class name and library configuration arrays as parameters, and * returns the full physical file path as output. * - `'resources'` _string_: If this is the default library, this maybe set to the * absolute path to the write-enabled application resources directory, which is used * for caching, log files, uploads, etc. * @return array Returns the resulting set of options created for this library. */ public static function add($name, array $config = []) { $defaults = [ 'name' => $name, 'path' => null, 'prefix' => $name . "\\", 'suffix' => '.php', 'loader' => null, 'includePath' => false, 'transform' => null, 'bootstrap' => true, 'defer' => false, 'default' => false ]; if ($name === 'lithium') { $defaults['defer'] = true; $defaults['bootstrap'] = false; $defaults['path'] = dirname(__DIR__); $defaults['loader'] = 'lithium\core\Libraries::load'; } if (isset($config['default']) && $config['default']) { static::$_default = $name; $defaults['path'] = LITHIUM_APP_PATH; $defaults['bootstrap'] = false; $defaults['resources'] = LITHIUM_APP_PATH . '/resources'; } $config += $defaults; if (!$config['path']) { if (!$config['path'] = static::_locatePath('libraries', compact('name'))) { throw new ConfigException("Library `{$name}` not found."); } } $config['path'] = str_replace('\\', '/', $config['path']); static::_configure(static::$_configurations[$name] = $config); return $config; } /** * Configures the application environment based on a library's settings, including appending to * the include path, loading a bootstrap file, and registering a loader with SPL's autoloading * system. * * @param array $config The new library's configuration array. * @return void */ protected static function _configure($config) { if ($config['includePath']) { $path = ($config['includePath'] === true) ? $config['path'] : $config['includePath']; set_include_path(get_include_path() . PATH_SEPARATOR . $path); } if ($config['bootstrap'] === true) { $path = "{$config['path']}/config/bootstrap.php"; $config['bootstrap'] = file_exists($path) ? 'config/bootstrap.php' : false; } if ($config['bootstrap']) { require "{$config['path']}/{$config['bootstrap']}"; } if (!empty($config['loader'])) { spl_autoload_register($config['loader']); } } /** * Allows library information to be retrieved in various ways, including: * * By name: * ``` embed:lithium\tests\cases\core\LibrariesTest::testLibraryConfigAccess(1-1) ``` * * With no parameters, to return all configuration for all libraries: * ``` embed:lithium\tests\cases\core\LibrariesTest::testLibraryConfigAccess(22-22) ``` * * By list of names with a key to extract: * ``` embed:lithium\tests\cases\core\LibrariesTest::testLibraryConfigAccess(34-34) ``` * * With no name, and a key to extract, to return a key/value array, where the library name is * the key, and the `$key` value is the value: * ``` embed:lithium\tests\cases\core\LibrariesTest::testLibraryConfigAccess(37-37) ``` * * By containing class name: * ``` embed:lithium\tests\cases\core\LibrariesTest::testLibraryConfigAccess(45-45) ``` * * @param mixed $name Either the name of a library added in `Libraries::add()`, an array of * library names, or a fully-namespaced class name (see usage examples above). * @param string $key Optional key name. If `$name` is set and is the name of a valid library * (or an array of valid libraries), returns the given named configuration key, * i.e. `'path'`, `'webroot'` or `'resources'`. * @return mixed A configuation array for one or more libraries, or a string value if `$key` is * specified and `$name` is a string, or a library name (string) if `$name` is a * fully-namespaced class name. */ public static function get($name = null, $key = null) { $configs = static::$_configurations; if (!$name && !$key) { return $configs; } if ($name === true) { $name = static::$_default; } if (is_array($name) || (!$name && $key)) { $name = $name ?: array_keys(static::$_configurations); $call = [get_called_class(), 'get']; return array_combine($name, array_map($call, $name, array_fill(0, count($name), $key))); } $config = isset($configs[$name]) ? $configs[$name] : null; if ($key) { return isset($config[$key]) ? $config[$key] : null; } if (strpos($name, '\\') === false) { return $config; } foreach (static::$_configurations as $library => $config) { if ($config['prefix'] && strpos($name, $config['prefix']) === 0) { return $library; } } } /** * Removes a registered library, and unregister's the library's autoloader, if it has one. * * @param mixed $name A string or array of library names indicating the libraries you wish to * remove, i.e. `'app'` or `'lithium'`. This can also be used to unload plugins by name. */ public static function remove($name) { foreach ((array) $name as $library) { if (isset(static::$_configurations[$library])) { if (static::$_configurations[$library]['loader']) { spl_autoload_unregister(static::$_configurations[$library]['loader']); } unset(static::$_configurations[$library]); } } } /** * Finds the classes or namespaces belonging to a particular library. _Note_: This method * assumes loaded class libraries use a consistent class-to-file naming convention. * * @param mixed $library The name of a library added to the application with `Libraries::add()`, * or `true` to search all libraries. * @param array $options The options this method accepts: * - `'path'` _string_: A physical filesystem path relative to the directory of the * library being searched. If provided, only the classes or namespaces within * this path will be returned. * - `'recursive'` _boolean_: If `true`, recursively searches all directories * (namespaces) in the given library. If `false` (the default), only searches the * top level of the given path. * - `'filter'` _string_: A regular expression applied to a class after it is * transformed into a fully-namespaced class name. The default regular expression * filters class names based on the * [PSR-0](http://groups.google.com/group/php-standards/web/psr-0-final-proposal) * PHP 5.3 naming standard. * - `'exclude'` _mixed_: Can be either a regular expression of classes/namespaces * to exclude, or a PHP callable to be used with `array_filter()`. * - `'namespaces'` _boolean_: Indicates whether namespaces should be included in * the search results. If `false` (the default), only classes are returned. * @return array Returns an array of fully-namespaced class names found in the given library or * libraries. * @todo Patch this to skip paths belonging to nested libraries in recursive searches. */ public static function find($library, array $options = []) { $format = function($file, $config) { $trim = [strlen($config['path']) + 1, strlen($config['suffix'])]; $rTrim = strpos($file, $config['suffix']) !== false ? -$trim[1] : 9999; $file = preg_split('/[\/\\\\]/', substr($file, $trim[0], $rTrim)); return $config['prefix'] . join('\\', $file); }; $defaults = compact('format') + [ 'path' => '', 'recursive' => false, 'filter' => '/^(\w+)?(\\\\[a-z0-9_]+)+\\\\[A-Z][a-zA-Z0-9]+$/', 'exclude' => '', 'namespaces' => false ]; $options += $defaults; $libs = []; if ($options['namespaces'] && $options['filter'] === $defaults['filter']) { $options['format'] = function($class, $config) use ($format, $defaults) { if (is_dir($class)) { return $format($class, $config); } if (preg_match($defaults['filter'], $class = $format($class, $config))) { return $class; } }; $options['filter'] = false; } if ($library === true) { foreach (static::$_configurations as $library => $config) { $libs = array_merge($libs, static::find($library, $options)); } return $libs; } if (!isset(static::$_configurations[$library])) { return null; } $config = static::$_configurations[$library]; $options['path'] = "{$config['path']}{$options['path']}/*"; $libs = static::_search($config, $options); return array_values(array_filter($libs)); } /** * Loads the class definition specified by `$class`. Looks through the list of libraries * defined in `$_configurations`, which are added through `lithium\core\Libraries::add()`. * * @see lithium\core\Libraries::add() * @see lithium\core\Libraries::path() * @param string $class The fully-namespaced (where applicable) name of the class to load. * @param boolean $require Specifies whether the class must be loaded or considered an * exception. Defaults to `false`. * @return void */ public static function load($class, $require = false) { $path = isset(static::$_cachedPaths[$class]) ? static::$_cachedPaths[$class] : null; $path = $path ?: static::path($class); if ($path && include $path) { static::$_cachedPaths[$class] = $path; if (method_exists($class, '__init')) { $message = "Support for automatic initialization of static classes has been "; $message .= "removed. `{$class}::__init()` exists, please remove it to get rid "; $message .= "of this message. Static classes must now be initialized manually. "; $message .= "i.e. by creating an `init()` method and calling it at the end of "; $message .= "the file and outside of the class."; throw new RuntimeException($message); } } elseif ($require) { throw new RuntimeException("Failed to load class `{$class}` from path `{$path}`."); } } /** * Associtates fully-namespaced class names to their corresponding paths on * the file system. * * Once a class is associtated to a path using `lithium\core\Libraries::map()` * the PSR-0 loader or custom class loader setted using the `transform` or `loader` * option of `lithium\core\Libraries::add()` are ignored and the associtated path * is used instead. * * @param array $classes An array of fully-namespaced class names (as keys) and * their correponding file's paths (as values). * @return void */ public static function map(array $classes) { foreach ($classes as $key => $value) { unset(static::$_cachedPaths[$key]); } static::$_map = array_merge(static::$_map, $classes); } /** * Unmap fully-namespaced class names mapped using `lithium\core\Libraries::map()`. * * @see lithium\core\Libraries::map() * @param mixed $classes An array of fully-namespaced class names or * a string with a fully-namespaced class name. */ public static function unmap($classes) { if (!is_array($classes)) { $classes = [$classes]; } foreach ($classes as $value) { unset(static::$_map[$value]); } } /** * Get the corresponding physical file path for a class or namespace name. * * @param string $class The class name to locate the physical file for. If `$options['dirs']` is * set to `true`, `$class` may also be a namespace name, in which case the corresponding * directory will be located. * @param array $options Options for converting `$class` to a physical path: * - `'dirs'`: Defaults to `false`. If `true`, will attempt to case-sensitively look up * directories in addition to files (in which case `$class` is assumed to actually be a * namespace). * @return string Returns the absolute path to the file containing `$class`, or `null` if the * file cannot be found. */ public static function path($class, array $options = []) { $defaults = ['dirs' => false]; $options += $defaults; $class = ltrim($class, '\\'); if (isset(static::$_cachedPaths[$class]) && !$options['dirs']) { return static::$_cachedPaths[$class]; } if (isset(static::$_map[$class]) && !$options['dirs']) { return static::$_map[$class]; } foreach (static::$_configurations as $name => $config) { $params = $options + $config; $suffix = $params['suffix']; if ($params['prefix'] && strpos($class, $params['prefix']) !== 0) { continue; } if ($transform = $params['transform']) { if ($file = static::_transformPath($transform, $class, $params)) { return $file; } continue; } $path = str_replace("\\", '/', substr($class, strlen($params['prefix']))); $fullPath = "{$params['path']}/{$path}"; if (!$options['dirs']) { return static::$_cachedPaths[$class] = static::realPath($fullPath . $suffix); } $list = glob(dirname($fullPath) . '/*'); $list = array_map(function($i) { return str_replace('\\', '/', $i); }, $list); if (in_array($fullPath . $suffix, $list)) { return static::$_cachedPaths[$class] = static::realPath($fullPath . $suffix); } return is_dir($fullPath) ? static::realPath($fullPath) : null; } } /** * Wraps the PHP `realpath()` function to add support for finding paths to files inside Phar * archives. * * @param string $path An unresolved path to a file inside a Phar archive which may or may not * exist. * @return string If `$path` is a valid path to a file inside a Phar archive, returns a string * in the format `'phar:///'`. Otherwise returns * `null`. */ public static function realPath($path) { if (($absolutePath = realpath($path)) !== false) { return $absolutePath; } if (!preg_match('%^phar://([^.]+\.phar(?:\.gz)?)(.+)%', $path, $pathComponents)) { return; } list(, $relativePath, $pharPath) = $pathComponents; $pharPath = implode('/', array_reduce(explode('/', $pharPath), function ($parts, $value) { if ($value === '..') { array_pop($parts); } elseif ($value !== '.') { $parts[] = $value; } return $parts; })); if (($resolvedPath = realpath($relativePath)) !== false) { if (file_exists($absolutePath = "phar://{$resolvedPath}{$pharPath}")) { return $absolutePath; } } } /** * Handles the conversion of a class name to a file name using a custom transformation typically * defined in the `'transform'` key of a configuration defined through `Libraries::add()`. * * The transformation can either be a closure which receives two parameters (the class name * as a string, and the library configuration as an array), or an array with two values (one * being the pattern to match, the other being the replacement). * * @see lithium\core\Libraries::add() * @see lithium\core\Libraries::path() * @param mixed $transform Either a closure or an array containing a regular expression match * and replacement. If the closure returns an empty value, or the regular * expression fails to match, will return `null`. * @param string $class The class name which is attempting to be mapped to a file. * @param array $options The configuration of the library as passed to `Libraries::add()`, along * with any options specified in the call to `Libraries::path()`. * @return string Returns transformed path of a class to a file, or `null` if the transformation * did not match. */ protected static function _transformPath($transform, $class, array $options = []) { if ((is_callable($transform)) && $file = $transform($class, $options)) { return $file; } if (is_array($transform)) { list($match, $replace) = $transform; return preg_replace($match, $replace, $class) ?: null; } } /** * Uses service location (i.e. `Libraries::locate()`) to look up a named class of a particular * type, and creates an instance of it, and passes an array of parameters to the constructor. * * If the given class can't be found, an exception is thrown. * * @param string $type The type of class as defined by `Libraries::$_paths`. * @param string|object $name The un- or fully namespaced name of the class to instantiate or * a name in the $classes array. Alternatively an already instantiated object, which will * be returned unmodified. * @param array $config An array of constructor parameters to pass to the class. * @param array $classes Map of short names to fully namespaced classes or instantiated objects, * to use for resolving short names. * @return object If the class is found, returns an instance of it. * @throws lithium\core\ClassNotFoundException Throws an exception if the class can't be found. * @filter */ public static function instance($type, $name, array $options = [], array $classes = []) { $params = compact('type', 'name', 'options', 'classes'); return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) { $name = $params['name']; $type = $params['type']; $classes = $params['classes']; if (is_string($name) && isset($classes[$name])) { $name = $classes[$name]; } if (is_object($name)) { return $name; } if (!$name && !$type) { $message = "Invalid class lookup: `\$name` and `\$type` are empty."; throw new ClassNotFoundException($message); } if (!is_string($type) && $type !== null && !isset(static::$_paths[$type])) { throw new ClassNotFoundException("Invalid class type `{$type}`."); } if (!$class = static::locate($type, $name)) { throw new ClassNotFoundException("Class `{$name}` of type `{$type}` not found."); } if (!(is_string($class) && class_exists($class))) { throw new ClassNotFoundException("Class `{$name}` of type `{$type}` not defined."); } return new $class($params['options']); }); } /** * Performs service location for an object of a specific type. If `$name` is a string, finds the * first instance of a class with the given name in any registered library (i.e. apps, plugins * or vendor libraries registered via `Libraries::add()`), based on each library's order of * precedence. For example, this will find the first model called `File` in any plugin or class * library loaded into an application, including the application itself. * * ``` * Libraries::locate('models', 'File'); * ``` * * Order of precedence is usually based on the order in which the library was registered (via * `Libraries::add()`), unless the library was registered with the `'defer'` option set to * `true`. All libraries with the `'defer'` option set will be searched in * registration-order **after** searching all libraries **without** `'defer'` set. This means * that in the above example, if an app and a plugin both have a model named `File`, then the * model from the app will be returned first, assuming the app was registered first (and * assuming the default settings). * * If `$name` is not specified, `locate()` returns an array with all classes of the specified * type which can be found. By default, `locate()` searches all registered libraries. * * ``` * Libraries::locate('models'); * ``` * * For example, the above will return an array of all model classes in all registered plugins * and libraries (including the app itself). * * To learn more about adding and modifying the class paths used with `locate()`, see the * documentation for the `paths()` method. * * @see lithium\core\Libraries::paths() * @see lithium\core\Libraries::add() * @see lithium\core\Libraries::_locateDeferred() * @param string $type The type of class to search for. Typically follows the name of the * directory in which the class is stored, i.e. `'models'`, `'controllers'` or * `'adapter'`. Some classes types, such as adapters, will require a greater * degree of specificity when looking up the desired class. In this case, the dot * syntax is used, as in this example when looking up cache adapters: * `'adapter.storage.cache'`, or this example, when looking up authentication * adapters: `'adapter.security.auth'`. * @param string $name The base name (without namespace) of the class you wish to locate. If * unspecified, `locate()` will attempt to find all classes of the type specified * in `$type`. If you only wish to search for classes within a single plugin or * library, you may use the dot syntax to prefix the class name with the library * name, i.e. `'app.Post'`, which will only look for a `Post` model within the * app itself. * @param array $options The options to use when searching and returning class names. * - `'type'` _string_: Defaults to `'class'`. If set to `'file'`, returns file * names instead of class names. * - `'library'` _string_: When specified, only the given library/plugin name will * be searched. * @return mixed If `$name` is specified, returns the name of the first class found that matches * `$name` and `$type`, or returns `null` if no matching classes were found in any * registered library. If `$name` is not specified, returns an array of all classes * found which match `$type`. */ public static function locate($type, $name = null, array $options = []) { if (is_object($name) || strpos($name, '\\') !== false) { return $name; } $ident = $name ? ($type . '.' . $name) : ($type . '.*'); $ident .= $options ? '.' . md5(serialize($options)) : null; if (isset(static::$_cachedPaths[$ident])) { return static::$_cachedPaths[$ident]; } $params = static::_params($type, $name); $defaults = [ 'type' => 'class', 'library' => $params['library'] !== '*' ? $params['library'] : null ]; $options += $defaults; unset($params['library']); $paths = static::paths($params['type']); if (!isset($paths)) { return null; } if ($params['name'] === '*') { $result = static::_locateAll($params, $options); return (static::$_cachedPaths[$ident] = $result); } if ($options['library']) { $result = static::_locateDeferred(null, $paths, $params, $options); return static::$_cachedPaths[$ident] = $result; } foreach ([false, true] as $defer) { if ($result = static::_locateDeferred($defer, $paths, $params, $options)) { return (static::$_cachedPaths[$ident] = $result); } } } /** * Returns or sets the the class path cache used for mapping class names to file paths, or * locating classes using `Libraries::locate()`. * * @param array $cache An array of keys and values to use when pre-populating the cache. Keys * are either class names (which match to file paths as values), or dot-separated * lookup paths used by `locate()` (which matches to either a single class or an * array of classes). If `false`, the cache is cleared. * @return array Returns an array of cached class lookups, formatted per the description for * `$cache`. */ public static function cache($cache = null) { if ($cache === false) { static::$_cachedPaths = []; } if (is_array($cache)) { static::$_cachedPaths += $cache; } return static::$_cachedPaths; } /** * Performs service location lookups by library, based on the library's `'defer'` flag. * Libraries with `'defer'` set to `true` will be searched last when looking up services. * * @see lithium\core\Libraries::$_paths * @see lithium\core\Libraries::locate() * @param boolean|null $defer A boolean flag indicating which libraries to search, either * the ones with the `'defer'` flag set, or the ones without. Providing `null` * will cause the method to ignore the `'defer'` flag set on any library and * perform a complete lookup. * @param array $paths List of paths to be searched for the given service (class). These are * defined in `lithium\core\Libraries::$_paths`, and are organized by class type. * @param array $params The list of insert parameters to be injected into each path format * string when searching for classes. * @param array $options * @return string Returns a class path as a string if a given class is found, or null if no * class in any path matching any of the parameters is located. */ protected static function _locateDeferred($defer, $paths, $params, array $options = []) { $libraries = static::$_configurations; if (isset($options['library'])) { $libraries = static::get((array) $options['library']); } foreach ($libraries as $library => $config) { if ($config['defer'] !== $defer && $defer !== null) { continue; } foreach (static::_searchPaths($paths, $library) as $tpl) { $params['library'] = rtrim($config['prefix'], '\\'); $class = str_replace('\\*', '', Text::insert($tpl, $params)); if (file_exists($file = Libraries::path($class, $options))) { return ($options['type'] === 'file') ? $file : $class; } } } } /** * Returns the list of valid search path templates for the given service location lookup. * * @see lithium\core\Libraries::$_paths * @see lithium\core\Libraries::_search() * @param array $paths The list of all possible path templates from `Libraries::$_paths`. * @param string $library The name of the library being searched. * @return array Returns an array of valid path template strings. */ protected static function _searchPaths($paths, $library) { $result = []; foreach ($paths as $tpl => $opts) { if (is_int($tpl)) { $tpl = $opts; $opts = []; } if (isset($opts['libraries']) && !in_array($library, (array) $opts['libraries'])) { continue; } $result[] = $tpl; } return $result; } /** * Locates all possible classes for given set of parameters. * * @param array $params * @param array $options * @return array */ protected static function _locateAll(array $params, array $options = []) { $defaults = ['libraries' => null, 'recursive' => true, 'namespaces' => false]; $options += $defaults; $paths = (array) static::$_paths[$params['type']]; $libraries = $options['library'] ? $options['library'] : $options['libraries']; $libraries = static::get((array) $libraries); $flags = ['escape' => '/']; $classes = []; foreach ($libraries as $library => $config) { $params['library'] = $config['path']; foreach (static::_searchPaths($paths, $library) as $tpl) { $options['path'] = str_replace('\\', '/', Text::insert($tpl, $params, $flags)); $options['path'] = str_replace('*/', '', $options['path']); $classes = array_merge($classes, static::_search($config, $options)); } } return array_unique($classes); } /** * Helper function for returning known paths given a certain type. * * @see lithium\core\Libraries::$_paths * @param string $type Path type (specified in `Libraries::$_paths`). * @param array $params Path parameters. * @return string|null Valid path name or `null` when no of path of given tpe is set. */ protected static function _locatePath($type, $params) { if (!isset(static::$_paths[$type])) { return null; } $params += ['app' => LITHIUM_APP_PATH, 'root' => LITHIUM_LIBRARY_PATH]; foreach (static::$_paths[$type] as $path) { if (is_dir($path = str_replace('\\', '/', Text::insert($path, $params)))) { return $path; } } } /** * Search file system. * * @param string $config * @param array $options * @param string $name * @return array */ protected static function _search($config, $options, $name = null) { $defaults = [ 'path' => null, 'suffix' => null, 'namespaces' => false, 'recursive' => false, 'preFilter' => '/[A-Z][A-Za-z0-9]+\./', 'filter' => false, 'exclude' => false, 'format' => function ($file, $config) { $trim = [strlen($config['path']) + 1, strlen($config['suffix'])]; $file = substr($file, $trim[0], -$trim[1]); return $config['prefix'] . str_replace('/', '\\', $file); } ]; $options += $defaults; $path = $options['path']; $suffix = $options['namespaces'] ? '' : $config['suffix']; $suffix = ($options['suffix'] === null) ? $suffix : $options['suffix']; $dFlags = GLOB_ONLYDIR; $zFlags = 0; if (strpos($path, '{') !== false) { $message = "Search path `{$path}` relies on brace globbing. "; $message .= 'Support for brace globbing in search paths has been deprecated.'; trigger_error($message, E_USER_DEPRECATED); $dFlags |= GLOB_BRACE; $zFlags |= GLOB_BRACE; } $libs = (array) glob($path . $suffix, $options['namespaces'] ? $dFlags : $zFlags); if ($options['recursive']) { list($current, $match) = explode('/*', $path, 2); $queue = array_diff((array) glob($current . '/*', $dFlags), $libs); $match = str_replace('##', '.+', preg_quote(str_replace('*', '##', $match), '/')); $match = '/' . $match . preg_quote($suffix, '/') . '$/'; while ($queue) { if (!is_dir($dir = array_pop($queue))) { continue; } $libs = array_merge($libs, (array) glob("{$dir}/*{$suffix}")); $queue = array_merge($queue, array_diff((array) glob("{$dir}/*", $dFlags), $libs)); } $libs = preg_grep($match, $libs); } if ($suffix) { $libs = $options['preFilter'] ? preg_grep($options['preFilter'], $libs) : $libs; } return static::_filter($libs, (array) $config, $options + compact('name')); } /** * Filters a list of library search results by the given set of options. * * @param array $libs List of found libraries. * @param array $config The configuration of the library currently being searched within. * @param array $options The options used to filter/format `$libs`. * @return array Returns a copy of `$libs`, filtered and transformed based on the configuration * provided in `$options`. */ protected static function _filter($libs, array $config, array $options = []) { if (is_callable($options['format'])) { foreach ($libs as $i => $file) { $libs[$i] = $options['format']($file, $config); } $libs = $options['name'] ? preg_grep("/{$options['name']}$/", $libs) : $libs; } if ($exclude = $options['exclude']) { if (is_string($exclude)) { $libs = preg_grep($exclude, $libs, PREG_GREP_INVERT); } elseif (is_callable($exclude)) { $libs = array_values(array_filter($libs, $exclude)); } } if ($filter = $options['filter']) { if (is_string($filter)) { $libs = preg_grep($filter, $libs) ; } elseif (is_callable($filter)) { $libs = array_filter(array_map($filter, $libs)); } } return $libs; } /** * Get params from type. * * @param string $type * @param string $name default: '*' * @return array type, namespace, class, name */ protected static function _params($type, $name = "*") { if (!$name) { $name = '*'; } $library = $namespace = $class = '*'; if (strpos($type, '.') !== false) { $parts = explode('.', $type); $type = array_shift($parts); switch (count($parts)) { case 1: list($class) = $parts; break; case 2: list($namespace, $class) = $parts; break; default: $class = array_pop($parts); $namespace = join('\\', $parts); break; } } if (strpos($name, '.') !== false) { $parts = explode('.', $name); $library = array_shift($parts); $name = array_pop($parts); $namespace = $parts ? join('\\', $parts) : "*"; } return compact('library', 'namespace', 'type', 'class', 'name'); } /* Deprecated / BC */ /** * Stores the closures that represent the method filters. They are indexed by method name. * * @deprecated Not used anymore. * @var array */ protected static $_methodFilters = []; /** * Apply a closure to a method in `Libraries`. * * @deprecated Replaced by `\lithium\aop\Filters::apply()` and `::clear()`. * @see lithium\util\collection\Filters * @param string $method The name of the method to apply the closure to. * @param Closure $filter The closure that is used to filter the method. * @return void */ public static function applyFilter($method, $filter = null) { $message = '`' . __METHOD__ . '()` has been deprecated in favor of '; $message .= '`\lithium\aop\Filters::apply()` and `::clear()`.'; trigger_error($message, E_USER_DEPRECATED); $class = get_called_class(); if ($method === false) { Filters::clear($class); return; } foreach ((array) $method as $m) { if ($filter === false) { Filters::clear($class, $m); } else { Filters::apply($class, $m, $filter); } } } } ?>