1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Consistency;
38
39/**
40 * Class Hoa\Consistency\Autoloader.
41 *
42 * This class is a PSR-4 compliant autoloader.
43 *
44 * @copyright  Copyright © 2007-2017 Hoa community
45 * @license    New BSD License
46 */
47class Autoloader
48{
49    /**
50     * Namespace prefixes to base directories.
51     *
52     * @var array
53     */
54    protected $_namespacePrefixesToBaseDirectories = [];
55
56
57
58    /**
59     * Add a base directory for a namespace prefix.
60     *
61     * @param   string  $prefix           Namespace prefix.
62     * @param   string  $baseDirectory    Base directory for this prefix.
63     * @param   bool    $prepend          Whether the prefix is prepended or
64     *                                    appended to the prefix' stack.
65     * @return  void
66     */
67    public function addNamespace($prefix, $baseDirectory, $prepend = false)
68    {
69        $prefix        = trim($prefix, '\\') . '\\';
70        $baseDirectory = rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
71
72        if (false === isset($this->_namespacePrefixesToBaseDirectories[$prefix])) {
73            $this->_namespacePrefixesToBaseDirectories[$prefix] = [];
74        }
75
76        if (true === $prepend) {
77            array_unshift(
78                $this->_namespacePrefixesToBaseDirectories[$prefix],
79                $baseDirectory
80            );
81        } else {
82            array_push(
83                $this->_namespacePrefixesToBaseDirectories[$prefix],
84                $baseDirectory
85            );
86        }
87
88        return;
89    }
90
91    /**
92     * Try to load the entity file for a given entity name.
93     *
94     * @param   string  $entity    Entity name to load.
95     * @return  bool
96     */
97    public function load($entity)
98    {
99        $entityPrefix     = $entity;
100        $hasBaseDirectory = false;
101
102        while (false !== $pos = strrpos($entityPrefix, '\\')) {
103            $currentEntityPrefix = substr($entity, 0, $pos + 1);
104            $entityPrefix        = rtrim($currentEntityPrefix, '\\');
105            $entitySuffix        = substr($entity, $pos + 1);
106            $entitySuffixAsPath  = str_replace('\\', '/', $entitySuffix);
107
108            if (false === $this->hasBaseDirectory($currentEntityPrefix)) {
109                continue;
110            }
111
112            $hasBaseDirectory = true;
113
114            foreach ($this->getBaseDirectories($currentEntityPrefix) as $baseDirectory) {
115                $file = $baseDirectory . $entitySuffixAsPath . '.php';
116
117                if (false !== $this->requireFile($file)) {
118                    return $file;
119                }
120            }
121        }
122
123        if (true    === $hasBaseDirectory &&
124            $entity === Consistency::getEntityShortestName($entity) &&
125            false   !== $pos = strrpos($entity, '\\')) {
126            return $this->runAutoloaderStack(
127                $entity . '\\' . substr($entity, $pos + 1)
128            );
129        }
130
131        return null;
132    }
133
134    /**
135     * Require a file if exists.
136     *
137     * @param   string  $filename    File name.
138     * @return  bool
139     */
140    public function requireFile($filename)
141    {
142        if (false === file_exists($filename)) {
143            return false;
144        }
145
146        require $filename;
147
148        return true;
149    }
150
151    /**
152     * Check whether at least one base directory exists for a namespace prefix.
153     *
154     * @param   string  $namespacePrefix    Namespace prefix.
155     * @return  bool
156     */
157    public function hasBaseDirectory($namespacePrefix)
158    {
159        return isset($this->_namespacePrefixesToBaseDirectories[$namespacePrefix]);
160    }
161
162    /**
163     * Get declared base directories for a namespace prefix.
164     *
165     * @param   string  $namespacePrefix    Namespace prefix.
166     * @return  array
167     */
168    public function getBaseDirectories($namespacePrefix)
169    {
170        if (false === $this->hasBaseDirectory($namespacePrefix)) {
171            return [];
172        }
173
174        return $this->_namespacePrefixesToBaseDirectories[$namespacePrefix];
175    }
176
177    /**
178     * Get loaded classes.
179     *
180     * @return  array
181     */
182    public static function getLoadedClasses()
183    {
184        return get_declared_classes();
185    }
186
187    /**
188     * Run the entire autoloader stack with a specific entity.
189     *
190     * @param   string  $entity    Entity name to load.
191     * @return  void
192     */
193    public function runAutoloaderStack($entity)
194    {
195        return spl_autoload_call($entity);
196    }
197
198    /**
199     * Register the autoloader.
200     *
201     * @param   bool  $prepend    Prepend this autoloader to the stack or not.
202     * @return  bool
203     */
204    public function register($prepend = false)
205    {
206        return spl_autoload_register([$this, 'load'], true, $prepend);
207    }
208
209    /**
210     * Unregister the autoloader.
211     *
212     * @return  bool
213     */
214    public function unregister()
215    {
216        return spl_autoload_unregister([$this, 'load']);
217    }
218
219    /**
220     * Get all registered autoloaders (not only from this library).
221     *
222     * @return  array
223     */
224    public function getRegisteredAutoloaders()
225    {
226        return spl_autoload_functions();
227    }
228
229    /**
230     * Dynamic new, a simple factory.
231     * It loads and constructs a class, with provided arguments.
232     *
233     * @param   bool     $classname    Classname.
234     * @param   array    $arguments    Arguments for the constructor.
235     * @return  object
236     */
237    public static function dnew($classname, array $arguments = [])
238    {
239        $classname = ltrim($classname, '\\');
240
241        if (false === Consistency::entityExists($classname, false)) {
242            spl_autoload_call($classname);
243        }
244
245        $class = new \ReflectionClass($classname);
246
247        if (empty($arguments) || false === $class->hasMethod('__construct')) {
248            return $class->newInstance();
249        }
250
251        return $class->newInstanceArgs($arguments);
252    }
253}
254
255/**
256 * Autoloader.
257 */
258$autoloader = new Autoloader();
259$autoloader->addNamespace('Hoa', dirname(__DIR__));
260$autoloader->register();
261