├── .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 | [](https://scrutinizer-ci.com/g/johnstevenson/statical/?branch=master)
4 | [](https://scrutinizer-ci.com/g/johnstevenson/statical/?branch=master)
5 | [](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 |
--------------------------------------------------------------------------------