├── .scrutinizer.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist └── src ├── AliasManager.php ├── BaseProxy.php ├── Input.php ├── Manager.php ├── NamespaceManager.php └── StaticalProxy.php /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'vendor/*' 4 | - 'tests/*' 5 | 6 | tools: 7 | external_code_coverage: 8 | enabled: true 9 | timeout: 900 10 | php_sim: 11 | enabled: true 12 | php_pdepend: 13 | enabled: true 14 | php_analyzer: 15 | enabled: true 16 | php_changetracking: 17 | enabled: true 18 | 19 | before_commands: 20 | - "composer install --prefer-source" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 John Stevenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Statical 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/johnstevenson/statical/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/johnstevenson/statical/?branch=master) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/johnstevenson/statical/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/johnstevenson/statical/?branch=master) 5 | [![Build Status](https://secure.travis-ci.org/johnstevenson/statical.png)](http://travis-ci.org/johnstevenson/statical) 6 | 7 | PHP static proxy library. 8 | ## Contents 9 | * [About](#About) 10 | * [Usage](#Usage) 11 | * [License](#License) 12 | 13 | 14 | ## About 15 | 16 | **Statical** is a tiny PHP library that enables you to call class methods from a static accessor, so 17 | the static call to `Foo::doSomething()` actually invokes the `doSomething()` method of a specific class 18 | instance. To show a more concrete example: 19 | 20 | ```php 21 | # Normal access 22 | $app->get('view')->render('mytemplate', $data); 23 | 24 | # Using a static proxy 25 | View::render('mytemplate', $data); 26 | ``` 27 | 28 | Both examples call the render method of the instantiated view class, with the static proxy version 29 | using terse and cleaner code. This may or may not be a good thing and depends entirely on your 30 | requirements, usage and point of view. 31 | 32 | ### How it Works 33 | Everything runs through the `Statical\Manager`. It needs three pieces of data to create 34 | each static proxy: 35 | 36 | * an alias *(which calls)* 37 | * a proxy class *(which invokes the method in)* 38 | * the target class 39 | 40 | An **alias** is the short name you use for method calling: `Foo`, `View` or whatever. 41 | 42 | You create a static **proxy class** like this. Note that its name is irrelevant and it is normally empty: 43 | 44 | ``` 45 | class FooProxy extends \Statical\BaseProxy {} 46 | ``` 47 | 48 | A **target class** is the class whose methods you wish to call. It can be either: 49 | 50 | * an actual class instance 51 | * a closure invoking a class instance 52 | * a reference to something in a container or service-locator that resolves to a class instance. 53 | 54 | This data is then registered using either the `addProxyInstance()` or the `addProxyService()` methods. 55 | See the [Usage](#Usage) section for some examples. 56 | 57 | ### Namespaces 58 | By default, each static proxy is registered in the global namespace. This means that any calls to 59 | `Foo` will not work in a namespace unless they are prefixed with a backslash `\Foo`. Alternatively 60 | you can include a *use* statement in each file: `use \Foo as Foo;`. 61 | 62 | **Statical** includes a powerful namespacing feature which allows you to add namespace patterns for 63 | an alias. For example `addNamespaceGroup('path', Foo', 'App\\Library')` allows you to call `Foo` in any 64 | *App\\Library* or descendant namespace. 65 | 66 | ### Features 67 | A few features in no particular order. Please see the [documentation][wiki] for more information. 68 | 69 | - **Statical** creates a static proxy to itself, aliased as *Statical* and available in any namespace, 70 | allowing you to call the Manager with `Statical::addProxyService()` or whatever. This feature can be disabled or modified as required. 71 | 72 | - You can use any type of container/service-locator. If it implements `ArrayAccess` or has a `get` method then **Statical** 73 | will discover this automatically, otherwise you need to pass a callable as the target container. 74 | 75 | - You can use multiple containers when adding proxy services to the Manager. 76 | 77 | - If you pass a closure as a proxy instance, it will be invoked once to resolve the target 78 | instance. You can get a reference to this instance, or in fact any target class, by calling the 79 | *getInstance()* method on your alias, for example `Foo::getInstance()`. 80 | 81 | - **Statical** is test-friendly. If you register a container then it is used to resolve the 82 | target instance for every proxied call, allowing you to swap in different objects. You can also 83 | replace a proxy by registering a different instance/container with the same alias. 84 | 85 | 86 | 87 | ## Usage 88 | Install via [composer][composer] 89 | 90 | ``` 91 | composer require statical/statical 92 | ``` 93 | 94 | Below are some examples. Firstly, using a class instance: 95 | 96 | ```php 97 | addProxyInstance($alias, $proxy, $instance); 107 | 108 | # Now we can call FooClass methods via the static alias Foo 109 | Foo::doSomething(); 110 | ``` 111 | 112 | For a container or service-locator you would do the following: 113 | 114 | ```php 115 | set($id, function ($c) { 124 | return new FooService($c); 125 | }); 126 | 127 | # Create our Manager 128 | $manager = new Statical\Manager(); 129 | 130 | # Add proxy service 131 | $manager->addProxyService($alias, $proxy, $container, $id); 132 | 133 | # FooService is resolved from the container each time Foo is called 134 | Foo::doSomething(); 135 | 136 | ``` 137 | 138 | If the container id is a lower-cased version of the alias, then you can omit the `$id` param. 139 | Using the above example: 140 | 141 | ```php 142 | addProxyService($alias, $proxy, $container); 151 | ... 152 | 153 | ``` 154 | 155 | In the above examples, the service is resolved out of the container automatically. If the container 156 | doesn't implement `ArrayAccess` or doesn't have a `get` method, you need to pass a callable: 157 | 158 | ```php 159 | foo = new FooService(); 165 | 166 | # so we need to pass a callable as the container param 167 | $container = array($di, '__get'); 168 | ... 169 | # Add proxy service 170 | $manager->addProxyService($alias, $proxy, $container); 171 | ... 172 | 173 | ``` 174 | Full usage [documentation][wiki] can be found in the Wiki. 175 | 176 | 177 | ## License 178 | 179 | Statical is licensed under the MIT License - see the `LICENSE` file for details 180 | 181 | 182 | [composer]: http://getcomposer.org 183 | [wiki]:https://github.com/johnstevenson/statical/wiki/Home 184 | 185 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statical/statical", 3 | "description": "Static proxy implementation", 4 | "keywords": ["static", "facade", "static proxy", "static interface", "statical"], 5 | "homepage": "http://github.com/johnstevenson/statical", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "John Stevenson", 10 | "email": "john-stevenson@blueyonder.co.uk" 11 | } 12 | ], 13 | 14 | "require": { 15 | "php": ">=5.3.3" 16 | }, 17 | 18 | "require-dev": { 19 | "phpunit/phpunit": "4.*" 20 | }, 21 | 22 | "autoload": { 23 | "psr-4": { 24 | "Statical\\": "src/" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | ./src 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/AliasManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Statical; 13 | 14 | use Statical\Input; 15 | 16 | class AliasManager 17 | { 18 | /** 19 | * Class aliases for lazy loading. 20 | * 21 | * @var array 22 | */ 23 | protected $aliases; 24 | 25 | /** 26 | * The namespace manager 27 | * 28 | * @var NamespaceManager 29 | */ 30 | protected $namespacer; 31 | 32 | /** 33 | * Whether to use namespacing 34 | * 35 | * @var bool 36 | */ 37 | protected $namespacing; 38 | 39 | /** 40 | * The autoloader callable 41 | * 42 | * @var callable 43 | */ 44 | protected $aliasLoader; 45 | 46 | /** 47 | * Constructor. 48 | * 49 | * @param boolean $namespacing 50 | */ 51 | public function __construct($namespacing) 52 | { 53 | $this->aliases = array(); 54 | $this->namespacer = new NamespaceManager(); 55 | $this->aliasLoader = array($this, 'loader'); 56 | $this->disable(); 57 | $this->namespacing = $namespacing; 58 | } 59 | 60 | /** 61 | * Adds a class alias to the aliases array 62 | * 63 | * 64 | * @param string $original 65 | * @param string $alias 66 | * @return void 67 | */ 68 | public function add($original, $alias) 69 | { 70 | $alias = Input::checkAlias($alias); 71 | $this->aliases[$alias] = $original; 72 | } 73 | 74 | /** 75 | * Adds a namespace 76 | * 77 | * @param string $alias 78 | * @param string[] $namespace 79 | */ 80 | public function addNamespace($alias, array $namespace) 81 | { 82 | $this->namespacer->add($alias, $namespace); 83 | } 84 | 85 | /** 86 | * Enables static proxying by registering the autoloader. 87 | * 88 | * Ensures that the autoloader is always at the end of the stack. 89 | * 90 | * @return void 91 | */ 92 | public function enable() 93 | { 94 | if ($this->isLoaderRegistered($last)) { 95 | if ($last) { 96 | return; 97 | } 98 | 99 | $this->disable(); 100 | } 101 | 102 | spl_autoload_register($this->aliasLoader); 103 | } 104 | 105 | /** 106 | * Disables static proxying. 107 | * 108 | * @return void 109 | */ 110 | public function disable() 111 | { 112 | spl_autoload_unregister($this->aliasLoader); 113 | $this->checkAutoloadStack(); 114 | } 115 | 116 | /** 117 | * Registered class loader to manage lazy class aliasing. 118 | * 119 | * @param string $class 120 | */ 121 | public function loader($class) 122 | { 123 | if (isset($this->aliases[$class])) { 124 | class_alias($this->aliases[$class], $class); 125 | return; 126 | } 127 | 128 | if ($this->namespacing) { 129 | if ($alias = $this->getNamespaceAlias($class)) { 130 | class_alias($this->aliases[$alias], $class); 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Returns the class alias if matched by namespacing. 137 | * 138 | * @param string $class 139 | * @return string|null 140 | */ 141 | protected function getNamespaceAlias($class) 142 | { 143 | if ($alias = $this->getRegisteredAlias($class)) { 144 | if ($this->namespacer->match($alias, $class)) { 145 | return $alias; 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * Returns the class alias if it is registered. 152 | * 153 | * @param string $class 154 | * @return string|null 155 | */ 156 | protected function getRegisteredAlias($class) 157 | { 158 | // Normalize to Unix path so we can use basename to remove the namespace 159 | $alias = basename(str_replace('\\', '/', $class)); 160 | 161 | return isset($this->aliases[$alias]) ? $alias : null; 162 | } 163 | 164 | /** 165 | * Reports whether the alias loader is registered and at the end of the stack. 166 | * 167 | * @param bool $last 168 | * @return bool 169 | */ 170 | protected function isLoaderRegistered(&$last) 171 | { 172 | $result = false; 173 | $last = false; 174 | 175 | if ($funcs = spl_autoload_functions()) { 176 | $index = array_search($this->aliasLoader, $funcs, true); 177 | 178 | if (false !== $index) { 179 | $result = true; 180 | $last = $index === count($funcs) - 1; 181 | } 182 | } 183 | 184 | return $result; 185 | } 186 | 187 | /** 188 | * Re-registers __autoload function if we have emptied the stack. 189 | * 190 | * @return void 191 | */ 192 | protected function checkAutoloadStack() 193 | { 194 | if (!spl_autoload_functions() && function_exists('__autoload')) { 195 | spl_autoload_register('__autoload'); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/BaseProxy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Statical; 13 | 14 | /** 15 | * Class that must be extended by all static proxies. 16 | */ 17 | abstract class BaseProxy 18 | { 19 | /** 20 | * The class instance to resolve proxy targets. 21 | * 22 | * @var object 23 | */ 24 | protected static $resolver; 25 | 26 | /** 27 | * Sets the statical resolver for child proxies. 28 | * 29 | * @param Manager $resolver 30 | * @return void 31 | */ 32 | public static function setResolver($resolver) 33 | { 34 | static::$resolver = $resolver; 35 | } 36 | 37 | /** 38 | * Returns the proxy target instance 39 | * 40 | * @return mixed 41 | */ 42 | public static function getInstance() 43 | { 44 | if (!is_object(static::$resolver)) { 45 | throw new \RuntimeException('Resolver not set'); 46 | } 47 | 48 | return static::$resolver->getProxyTarget(get_called_class()); 49 | } 50 | 51 | /** 52 | * Built-in magic method through which child class calls pass. 53 | * 54 | * @param string $name 55 | * @param array $args 56 | * @return mixed 57 | */ 58 | public static function __callStatic($name, $args) 59 | { 60 | return call_user_func_array(array(static::getInstance(), $name), $args); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Input.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Statical; 13 | 14 | /** 15 | * This class provides functions to check various input values and throw an 16 | * InvalidArgumentException on failure. 17 | * 18 | * All functions are static, enabling their use from various component classes. 19 | */ 20 | class Input 21 | { 22 | /** 23 | * Checks that an alias has no backslash characters 24 | * 25 | * @param string $value 26 | * @throws InvalidArgumentException 27 | * @return string 28 | */ 29 | public static function checkAlias($value) 30 | { 31 | $value = static::check($value); 32 | 33 | if (strpos($value, '\\')) { 34 | throw new \InvalidArgumentException('Alias must not be namespaced.'); 35 | } 36 | 37 | return $value; 38 | } 39 | 40 | /** 41 | * Checks that namespace has no leading or trailing backslashes 42 | * 43 | * @param string $value 44 | * @throws InvalidArgumentException 45 | * @return string 46 | */ 47 | public static function checkNamespace($value) 48 | { 49 | $value = static::check($value); 50 | 51 | if (0 === strpos($value, '\\') || '\\' === substr($value, -1)) { 52 | throw new \InvalidArgumentException('Invalid namespace.'); 53 | } 54 | 55 | return $value; 56 | } 57 | 58 | /** 59 | * Checks that the container is valid 60 | * 61 | * @param mixed $container 62 | * @throws InvalidArgumentException 63 | * @return callable 64 | */ 65 | public static function checkContainer($container) 66 | { 67 | $result = static::formatContainer($container); 68 | 69 | if (!is_callable($result)) { 70 | throw new \InvalidArgumentException('Container must be a callable.'); 71 | } 72 | 73 | return $result; 74 | } 75 | 76 | /** 77 | * Formats and returns a namespace param. 78 | * 79 | * @param mixed $namespace Either a string or array of namespaces 80 | * @param mixed $group 81 | */ 82 | public static function formatNamespace($namespace, $group = null) 83 | { 84 | $namespace = (array) $namespace; 85 | 86 | if ($group) { 87 | $namespace = static::formatNamespaceGroup($namespace, $group); 88 | } 89 | 90 | return $namespace; 91 | } 92 | 93 | /** 94 | * Checks that a value is a string and not empty 95 | * 96 | * @param string $value 97 | * @throws InvalidArgumentException 98 | * @return string 99 | */ 100 | protected static function check($value) 101 | { 102 | if (!is_string($value) || !$value) { 103 | throw new \InvalidArgumentException('Empty or invalid value.'); 104 | } 105 | 106 | return $value; 107 | } 108 | 109 | /** 110 | * Checks that group is valid. 111 | * 112 | * @param string $value 113 | * @throws InvalidArgumentException 114 | * @return string 115 | */ 116 | protected static function checkGroup($value) 117 | { 118 | $groups = array('any', 'path', 'name'); 119 | 120 | if (!in_array($value, $groups, true)) { 121 | throw new \InvalidArgumentException('Invalid namespace group.'); 122 | } 123 | 124 | return $value; 125 | } 126 | 127 | /** 128 | * Formats a container param as a callable. 129 | * 130 | * @param mixed $container 131 | * @return callable 132 | */ 133 | protected static function formatContainer($container) 134 | { 135 | if (!is_array($container)) { 136 | $container = array($container); 137 | } 138 | 139 | $instance = array_shift($container); 140 | 141 | if (!$method = array_shift($container)) { 142 | if ($instance instanceof \ArrayAccess) { 143 | $method = 'offsetGet'; 144 | } else { 145 | $method = 'get'; 146 | } 147 | } 148 | 149 | return array($instance, $method); 150 | } 151 | 152 | /** 153 | * Formats and returns a namespace param. 154 | * 155 | * @param string[] $namespace 156 | * @param string $group 157 | */ 158 | protected static function formatNamespaceGroup(array $namespace, $group) 159 | { 160 | $group = static::checkGroup($group); 161 | 162 | if ('any' === $group) { 163 | $namespace = array('*'); 164 | } else { 165 | foreach ($namespace as &$value) { 166 | $value = static::checkNamespace($value); 167 | if ('path' === $group) { 168 | $value .= '\\*'; 169 | } 170 | } 171 | } 172 | 173 | return $namespace; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Manager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Statical; 13 | 14 | use Statical\Input; 15 | 16 | class Manager 17 | { 18 | /** 19 | * The static classes to proxy. 20 | * 21 | * @var array 22 | */ 23 | protected $registry = array(); 24 | 25 | /** 26 | * The alias manager 27 | * 28 | * @var \Statical\AliasManager 29 | */ 30 | protected $aliasManager; 31 | 32 | /** 33 | * Whether the class is to be treated as a singleton. 34 | * 35 | * @var bool 36 | */ 37 | public static $singleton = false; 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * Creating more than one instance could lead to unpredictable behaviour. 43 | * To enforce a singleton pattern call the makeSingleton() method. 44 | * 45 | * The optional params allow the default way to start the Manager to be 46 | * overriden. 47 | * 48 | * By default the service will be enabled and a proxy to ourself will be 49 | * created, aliased as Statical and available in any namespace. To modify 50 | * this behaviour pass one of the following to $bootMode: 51 | * 52 | * 'enable' - the service is enabled 53 | * 'none' - no action, the implementation must call enable() 54 | * 55 | * The namespacing feature is on by default, but can be disabled here if it 56 | * is not required, or suitable. 57 | * 58 | * @param mixed $bootMode Either 'enable' or 'none' if specified 59 | * @param boolean $namespacing Whether namespacing should be allowed 60 | * @throws RuntimeException if we we have been set as a singleton 61 | */ 62 | public function __construct($bootMode = null, $namespacing = true) 63 | { 64 | if (static::$singleton) { 65 | throw new \RuntimeException(__CLASS__ . ' has been set as a singleton.'); 66 | } 67 | 68 | BaseProxy::setResolver($this); 69 | $this->aliasManager = new AliasManager($namespacing); 70 | $this->boot($bootMode); 71 | } 72 | 73 | /** 74 | * Registers ourself as a proxy, aliased as Statical, and enables the service. 75 | * 76 | * @param mixed $namespace Either a string or array of namespaces 77 | * @return void 78 | */ 79 | public function addProxySelf($namespace = null) 80 | { 81 | $this->addProxyInstance('Statical', 'Statical\\StaticalProxy', $this); 82 | 83 | if ($namespace) { 84 | $this->addNamespace('Statical', $namespace); 85 | } 86 | 87 | $this->enable(); 88 | } 89 | 90 | /** 91 | * Adds a service as a proxy target 92 | * 93 | * If $id is null then the lower-cased alias will be used. 94 | * 95 | * @param string $alias The statical name you call 96 | * @param string $proxy The namespaced proxy class 97 | * @param mixed $container Reference to a container 98 | * @param mixed $id The id of the target in the container 99 | * @param mixed $namespace Optional namespace 100 | */ 101 | public function addProxyService($alias, $proxy, $container, $id = null, $namespace = null) 102 | { 103 | $proxy = Input::checkNamespace($proxy); 104 | $container = Input::checkContainer($container); 105 | $id = $id ?: strtolower($alias); 106 | 107 | $this->addProxy($proxy, $id, $container); 108 | $this->aliasManager->add($proxy, $alias); 109 | 110 | if ($namespace) { 111 | $this->addNamespace($alias, $namespace); 112 | } 113 | } 114 | 115 | /** 116 | * Adds an instance or closure as a proxy target 117 | * 118 | * @param string $alias The statical name you call 119 | * @param string $proxy The namespaced proxy class 120 | * @param mixed $target The target instance or closure 121 | * @param mixed $namespace Optional namespace 122 | */ 123 | public function addProxyInstance($alias, $proxy, $target, $namespace = null) 124 | { 125 | $proxy = Input::checkNamespace($proxy); 126 | 127 | if (!is_object($target)) { 128 | throw new \InvalidArgumentException('Target must be an instance or closure.'); 129 | } 130 | 131 | $this->addProxy($proxy, null, $target); 132 | $this->aliasManager->add($proxy, $alias); 133 | 134 | if ($namespace) { 135 | $this->addNamespace($alias, $namespace); 136 | } 137 | } 138 | 139 | /** 140 | * Adds a namespace for a single, or all aliases 141 | * 142 | * The $alias can either be a single value or the wildcard '*' value, which 143 | * allows any registered alias in the namespace. 144 | * 145 | * Namespace can either be a single string value or an array of values and 146 | * are formatted as follows: 147 | * 148 | * 'App' - the alias can be called in the App namespace 149 | * 'App\\*' - the alias can be called in the App or descendant namespaces 150 | * '*' - the alias can be called in any namespace 151 | * 152 | * @param string $alias 153 | * @param mixed $namespace 154 | */ 155 | public function addNamespace($alias, $namespace) 156 | { 157 | $namespace = Input::formatNamespace($namespace); 158 | $this->aliasManager->addNamespace($alias, $namespace); 159 | } 160 | 161 | /** 162 | * Adds a namespace group for a single, or all aliases 163 | * 164 | * The $alias can either be a single value or the wildcard '*' value, which 165 | * allows any registered alias in the namespace. 166 | * 167 | * The group can be one of the following: 168 | * 169 | * 'name' - the alias can be called in the $namespace 170 | * 'path' - the alias can be called in the $namespace and any descendants 171 | * 'any' - the alias can be called in any namespace 172 | * 173 | * Namespace can either be a single string value, an array of values, or 174 | * missing in the case of group 'any'. 175 | * 176 | * @param string $group 177 | * @param string $alias 178 | * @param mixed $namespace 179 | */ 180 | public function addNamespaceGroup($group, $alias, $namespace = null) 181 | { 182 | $namespace = Input::formatNamespace($namespace, $group); 183 | $this->aliasManager->addNamespace($alias, $namespace); 184 | } 185 | 186 | /** 187 | * Enables static proxying by registering the autoloader. 188 | * 189 | * Ensures that the autoloader is always at the end of the stack. This can 190 | * be useful if another part of the code base adds its own autoloader after 191 | * the Statical one. Keeping the autoloader at the end reduces the risk of 192 | * conflicts with other libraries. 193 | * 194 | * This method can be called multiple times, because it does nothing if 195 | * the autoloader is in its correct place. 196 | * 197 | * @return void 198 | */ 199 | public function enable() 200 | { 201 | $this->aliasManager->enable(); 202 | } 203 | 204 | /** 205 | * Disables static proxying. 206 | * 207 | * Included for completeness, in case there is a requirement to stop 208 | * Statical. The autoloader is removed from the stack, but this will not 209 | * affect any classes that have already been loaded. 210 | * 211 | * @return void 212 | */ 213 | public function disable() 214 | { 215 | $this->aliasManager->disable(); 216 | } 217 | 218 | /** 219 | * Enforces a singeton pattern on the class. 220 | * 221 | * Subsequent attempts to instantiate this class will throw an exception. 222 | * 223 | * @return void 224 | */ 225 | public function makeSingleton() 226 | { 227 | static::$singleton = true; 228 | } 229 | 230 | /** 231 | * Returns the target instance of the static proxy. 232 | * 233 | * This function is called from Statical\BaseProxy to resolve proxy targets. 234 | * 235 | * @param string $class 236 | * @throws RuntimeException 237 | * @return mixed 238 | */ 239 | public function getProxyTarget($class) 240 | { 241 | if (!isset($this->registry[$class])) { 242 | throw new \RuntimeException($class.' not registered as a static proxy.'); 243 | } 244 | 245 | if ($id = $this->registry[$class]['id']) { 246 | return call_user_func_array($this->registry[$class]['target'], array($id)); 247 | } else { 248 | 249 | if ($closure = $this->registry[$class]['closure']) { 250 | $this->registry[$class]['target'] = $closure(); 251 | $this->registry[$class]['closure'] = null; 252 | } 253 | 254 | return $this->registry[$class]['target']; 255 | } 256 | } 257 | 258 | /** 259 | * Adds a proxy to the registry array 260 | * 261 | * Since this is called internally it assumes that the inputs are correct. 262 | * 263 | * @param string $proxy 264 | * @param string $id 265 | * @param mixed $target 266 | * @return void 267 | */ 268 | protected function addProxy($proxy, $id, $target) 269 | { 270 | $callee = $target instanceof \Closure ? null : $target; 271 | $closure = $callee ? null : $target; 272 | 273 | $this->registry[$proxy] = array( 274 | 'id' => $id, 275 | 'target' => $callee, 276 | 'closure' => $closure 277 | ); 278 | } 279 | 280 | /** 281 | * Starts the Manager with either the default or more restricted settings. 282 | * 283 | * @param mixed $bootMode 284 | * $return void 285 | */ 286 | protected function boot($bootMode) 287 | { 288 | switch ($bootMode) { 289 | case 'enable': 290 | // Enable the service 291 | $this->enable(); 292 | break; 293 | case 'none': 294 | // Do nothing, the implementation will deal with it 295 | break; 296 | default: 297 | // Enable the service and register ourself in any namespace 298 | $this->addProxySelf('*'); 299 | break; 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/NamespaceManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Statical; 13 | 14 | use Statical\Input; 15 | 16 | class NamespaceManager 17 | { 18 | /** 19 | * Namespaces to modify lazy loading. 20 | * 21 | * @var array 22 | */ 23 | protected $namespaces = array(); 24 | 25 | /** 26 | * Adds a namespace 27 | * 28 | * @param string $alias 29 | * @param string[] $namespace 30 | */ 31 | public function add($alias, array $namespace) 32 | { 33 | $alias = Input::checkAlias($alias); 34 | $props = $this->getNamespace($alias, true); 35 | 36 | foreach ($namespace as $ns) { 37 | $group = $this->getNamespaceGroup($ns); 38 | 39 | if ('any' === $group) { 40 | $props = array('any' => true); 41 | break; 42 | } else { 43 | // trim trailing * from path pattern 44 | $ns = 'path' === $group ? rtrim($ns, '*') : $ns; 45 | $props[$group][] = $ns; 46 | } 47 | } 48 | 49 | $this->setNamespace($alias, $props); 50 | } 51 | 52 | /** 53 | * Returns true if a matching namespace is found. 54 | * 55 | * @param string $alias 56 | * @param string $class 57 | * @return bool 58 | */ 59 | public function match($alias, $class) 60 | { 61 | foreach (array('*', $alias) as $key) { 62 | if ($props = $this->getNamespace($key)) { 63 | if ($this->matchGroup($props, $alias, $class)) { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * Returns true if a namespace entry is matched. 74 | * 75 | * @param array $props 76 | * @param string $alias 77 | * @param string $class 78 | * @return bool 79 | */ 80 | protected function matchGroup($props, $alias, $class) 81 | { 82 | if ($props['any']) { 83 | return true; 84 | } 85 | 86 | foreach (array('path', 'name') as $group) { 87 | if ($this->matchClass($props[$group], $group, $alias, $class)) { 88 | return true; 89 | } 90 | } 91 | 92 | return false; 93 | } 94 | 95 | /** 96 | * Returns true if a class matches a namespace item 97 | * 98 | * @param array $array 99 | * @param string $group 100 | * @param string $alias 101 | * @param string $class 102 | */ 103 | protected function matchClass($array, $group, $alias, $class) 104 | { 105 | $match = false; 106 | 107 | foreach ($array as $test) { 108 | 109 | if ('path' === $group) { 110 | $match = 0 === strpos($class, $test); 111 | } else { 112 | $match = $test.'\\'.$alias === $class; 113 | } 114 | 115 | if ($match) { 116 | break; 117 | } 118 | } 119 | 120 | return $match; 121 | } 122 | 123 | /** 124 | * Returns the namespace groups for an alias. 125 | * 126 | * Returns either an empty array if the alias is not found and $default is 127 | * false, or an array containing all namespace groups with any found values. 128 | * 129 | * @param string $alias 130 | * @param bool $default 131 | * @return array 132 | */ 133 | protected function getNamespace($alias, $default = false) 134 | { 135 | $result = isset($this->namespaces[$alias]) ? $this->namespaces[$alias] : array(); 136 | 137 | if ($result || $default) { 138 | $result = array_merge($this->getDefaultGroups(), $result); 139 | } 140 | 141 | return $result; 142 | } 143 | 144 | /** 145 | * Adds a namespace array group to the namespaces array. 146 | * 147 | * If the group is an array, duplicate entries are removed. Empty groups 148 | * are removed from the final array entry. 149 | * 150 | * @param string $alias 151 | * @param array $props 152 | * @return void 153 | */ 154 | protected function setNamespace($alias, $props) 155 | { 156 | array_walk($props, function (&$value) { 157 | if (is_array($value)) { 158 | $value = array_unique($value); 159 | } 160 | }); 161 | 162 | $this->namespaces[$alias] = array_filter($props); 163 | } 164 | 165 | /** 166 | * Returns the group name for the namespace input type. 167 | * 168 | * @param string $namespace 169 | * @return string 170 | */ 171 | protected function getNamespaceGroup($namespace) 172 | { 173 | $namespace = Input::checkNamespace($namespace); 174 | 175 | if ('*' === substr($namespace, -1)) { 176 | if ('*' === $namespace) { 177 | $group = 'any'; 178 | } else { 179 | $group = 'path'; 180 | } 181 | } else { 182 | $group = 'name'; 183 | } 184 | 185 | return $group; 186 | } 187 | 188 | /** 189 | * Returns an array of default groups. 190 | * 191 | * @return array 192 | */ 193 | protected function getDefaultGroups() 194 | { 195 | return array( 196 | 'any' => false, 197 | 'path' => array(), 198 | 'name' => array() 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/StaticalProxy.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | /** 12 | * Statical proxy for the main Statical manager class 13 | */ 14 | namespace Statical; 15 | 16 | class StaticalProxy extends BaseProxy 17 | { 18 | } 19 | --------------------------------------------------------------------------------