Platform  3.1
PHP API documentation
 All Data Structures Namespaces Files Functions Variables Pages
classAutoloader.php
Go to the documentation of this file.
1 <?php
2 /*
3  * @author Anakeen
4  * @license http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ Anakeen - licence CC
5  * @package FDL
6 */
7 
8 namespace dcp;
9 
10 class ExtensionFilterIteratorDecorator extends \FilterIterator
11 {
12  private $_ext;
13  /**
14  * Check whether the current element of the iterator is acceptable
15  *
16  * @see FilterIterator::accept()
17  *
18  * @return bool true if the current element is acceptable, otherwise false.
19  *
20  */
21  public function accept()
22  {
23  if (substr($this->current() , -1 * strlen($this->_ext)) === $this->_ext) {
24  return is_readable($this->current());
25  }
26  return false;
27  }
28  /**
29  * define which extension is allowed
30  *
31  * @param string $pExt extension
32  *
33  * @return void
34  */
35  public function setExtension($pExt)
36  {
37  $this->_ext = $pExt;
38  }
39 }
40 /**
41  * Interface for classes in charge of detecting classes declarations in files
42  *
43  * @author dev@dynacase.org <dev@dynacase.org>
44  *
45  */
46 interface IClassHunter {
47  /**
48  * Find all classes in given file
49  *
50  * @param text $pFileName file name
51  *
52  * @return array of strings
53  *
54  */
55  public function find($pFileName);
56 }
57 /**
58  * Class in charge of detecting classes declarations in files (only for PHP 5.3)
59  *
60  * @author dev@dynacase.org <dev@dynacase.org>
61  *
62  */
64 {
65  /**
66  * Find all classes in given file
67  *
68  * @param text $pFileName file name
69  *
70  * @return array of strings
71  *
72  */
73  public function find($pFileName)
74  {
75  $toReturn = array();
76  $tokens = token_get_all(file_get_contents($pFileName, false));
77  $currentNamespace = '';
78  $namespaceHunt = false;
79  $validatedNamespaceHunt = false;
80  $classHunt = false;
81  $whitespaceCount = 0;
82  foreach ($tokens as $token) {
83  if (is_array($token)) {
84  if ($token[0] === T_INTERFACE || $token[0] === T_CLASS) {
85  $classHunt = true;
86  continue;
87  } elseif ($token[0] === T_NAMESPACE) {
88  $namespaceHunt = true;
89  continue;
90  }
91  if ($classHunt && $token[0] === T_STRING) {
92  $toReturn[(strlen($currentNamespace) > 0 ? $currentNamespace . '\\' : '') . $token[1]] = $pFileName;
93  $classHunt = false;
94  } elseif ($namespaceHunt && $validatedNamespaceHunt && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) {
95  $currentNamespace.= $token[1];
96  } elseif ($namespaceHunt && !$validatedNamespaceHunt && $token[0] === T_WHITESPACE) {
97  $currentNamespace = '';
98  $validatedNamespaceHunt = true;
99  } elseif ($namespaceHunt && !$validatedNamespaceHunt && $token[0] !== T_WHITESPACE) {
100  $namespaceHunt = false;
101  }
102  } else {
103  if ($token === ';' || $token === '{') {
104  //can validate namespace only when using namespace{} syntax
105  if ($namespaceHunt && !$validatedNamespaceHunt && $token === '{') {
106  $currentNamespace = '';
107  }
108  $classHunt = false;
109  $namespaceHunt = false;
110  $validatedNamespaceHunt = false;
111  }
112  }
113  }
114  return $toReturn;
115  }
116 }
117 
118 class DirectoriesAutoloaderException extends \Exception
119 {
120 }
122 {
123  private $_classHunterStrategy;
124  //--- Singleton
125 
126  /**
127  * make constructor private
128  *
129  * @return void
130  */
131  private function __construct()
132  {
133  }
134  /**
135  *
136  * @var DirectoriesAutoloader
137  */
138  private static $_instance = false;
139  /**
140  * get singleton for DirectoriesAutoloader
141  *
142  * @param string $pTmpPath path to tmp dir
143  * @param string $pTmpFileName tmp file name
144  *
145  * @return dcp\DirectoriesAutoloader
146  */
147  public static function instance($pTmpPath, $pTmpFileName = 'directoriesautoloader.cache.php')
148  {
149  if (self::$_instance === false) {
150  self::$_instance = new DirectoriesAutoloader();
151  self::$_instance->setCachePath($pTmpPath);
152  self::$_instance->setCacheFileName($pTmpFileName);
153  self::$_instance->_classHunterStrategy = ClassHunterFactory::create(PHP_VERSION);
154  }
155  return self::$_instance;
156  }
157  //--- /Singleton
158 
159  /**
160  * register autoloader against SPL
161  *
162  * @return boolean
163  */
164  public function register()
165  {
166  return spl_autoload_register(array(
167  $this,
168  'autoload'
169  ));
170  }
171  /**
172  * force autoloader to regenerate cache now!
173  *
174  * @return dcp\DirectoriesAutoloader
175  */
176  public function forceRegenerate()
177  {
178  $this->_canRegenerate = true;
179  $this->_classes = null;
180  $this->_saveIncache();
181  return self::$_instance;
182  }
183  //--- Cache
184  private $_cachePath;
185  private $_cacheFileName = 'directoriesautoloader.cache.php';
186  /**
187  * define the path of tmp directory
188  *
189  * @param string $pTmp cache directory path
190  *
191  * @throws \dcp\DirectoriesAutoloaderException
192  *
193  * @return void
194  */
195  public function setCachePath($pTmp)
196  {
197  if (!is_writable($pTmp)) {
198  throw new DirectoriesAutoloaderException('Cannot write in given CachePath [' . $pTmp . ']');
199  }
200  $this->_cachePath = $pTmp;
201  }
202  /**
203  * define the name of tmp file
204  *
205  * @param string $ptmpFileName tmp file name
206  *
207  * @return void
208  */
209  public function setCacheFileName($ptmpFileName)
210  {
211  $this->_cacheFileName = $ptmpFileName;
212  }
213  /**
214  * get the fully qualified tmp file path
215  *
216  * @return string
217  */
218  public function getCacheFilePath()
219  {
220  return $this->_cachePath . '/' . $this->_cacheFileName;
221  }
222  //--- /Cache
223  //--- custom filters
224  private $_customFilterClasses = null;
225  /**
226  * add an instance of FilterIterator as custom filter
227  *
228  * @param string $pCustomFilterClass className of customFilter to add
229  *
230  * @throws dcp\DirectoriesAutoloaderException
231  *
232  * @return dcp\DirectoriesAutoloader
233  */
234  public function addCustomFilter($pCustomFilterClass)
235  {
236  $filterClassParents = class_parents($pCustomFilterClass);
237  if (isset($filterClassParents['FilterIterator'])) {
238  if (!is_array($this->_customFilterClasses)) {
239  $this->_customFilterClasses = array(
240  $pCustomFilterClass
241  );
242  //error_log("adding [$pCustomFilterClass] as custom filter");
243 
244  } else {
245  $this->_customFilterClasses[] = $pCustomFilterClass;
246  }
247  } else {
248  throw new DirectoriesAutoloaderException('Custom filter class [' . $pCustomFilterClass . '] does not inherits from FilterIterator');
249  }
250  return self::$_instance;
251  }
252  //--- /custom filters
253  //--- Autoload
254 
255  /**
256  * autoloader
257  *
258  * @param string $pClassName name of the class to load
259  *
260  * @return boolean
261  */
262  public function autoload($pClassName)
263  {
264  //do we already know this class?
265  if ($this->_loadClass($pClassName)) {
266  return true;
267  }
268  //If we are allowed to regenerate autoload file, we try again
269  if ($this->_canRegenerate) {
270  $this->_canRegenerate = false; //avoid loops
271  $this->_includesAll();
272  $this->_saveInCache();
273  return $this->autoload($pClassName);
274  }
275  //we really found nothing
276  return false;
277  }
278  private $_canRegenerate = true;
279  //--- /Autoload
280 
281  /**
282  * look for all classes into registered directories
283  *
284  * @return void
285  */
286  private function _includesAll()
287  {
288  $cwd = getcwd();
289  //include known classes
290  foreach ($this->_directories as $directory => $recursive) {
291  /*
292  * Relative directories are handled relative to _cachePath
293  */
294  $changedCwd = false;
295  if (strpos($directory, '/') !== 0) {
296  $ret = chdir($this->_cachePath . DIRECTORY_SEPARATOR . $directory);
297  if ($ret === false) {
298  continue;
299  }
300  $changedCwd = true;
301  }
302 
303  $directories = new \AppendIterator();
304  //add all paths that we want to browse
305  if ($recursive) {
306  $directories->append(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)));
307  } else {
308  $directories->append(new \DirectoryIterator($directory));
309  }
310  if (is_array($this->_customFilterClasses)) {
311  foreach ($this->_customFilterClasses as $customFilterClass) {
312  //error_log("trying to use [$customFilterClass] as filter");
313  //error_log("directories was a " . get_class($directories));
314  $directories = new $customFilterClass($directories);
315  //error_log("directories is now a " . get_class($directories));
316 
317  }
318  }
319  //restrict files to php ones
320  $files = new ExtensionFilterIteratorDecorator($directories);
321  $files->setExtension('.php');
322  foreach ($files as $fileName) {
323  $classes = $this->_classHunterStrategy->find((string)$fileName);
324  foreach ($classes as $className => $fileName) {
325  $this->_classes[strtolower($className) ] = $fileName;
326  }
327  }
328 
329  if ($changedCwd) {
330  chdir($cwd);
331  }
332  }
333  //error_log('included all classes as ' . var_export($this->_classes, true));
334 
335  }
336  /**
337  * array of files containing classes indexed by class name
338  *
339  * @var array $_classes
340  */
341  private $_classes = array();
342  /**
343  * write cache file
344  *
345  * @see DirectoriesAutoloader::_classes
346  * @see DirectoriesAutoloader::getCacheFilePath
347  *
348  * @throws dcp\DirectoriesAutoloaderException
349  *
350  * @return void
351  */
352  private function _saveIncache()
353  {
354  foreach ($this->_classes as $className => & $fileName) {
355  if (substr($fileName, 0, 2) == './') {
356  $fileName = substr($fileName, 2);
357  }
358  }
359  unset($fileName);
360  $toSave = '<?php' . PHP_EOL;
361  $toSave.= '// Cache generated at: ' . date(DATE_W3C) . PHP_EOL;
362  $toSave.= '$classes = ' . var_export($this->_classes, true);
363  $toSave.= '; ?>';
364  //error_log('will save classes');
365  if (file_put_contents($this->getCacheFilePath() , $toSave) === false) {
366  throw new DirectoriesAutoloaderException('Cannot write cache file ' . $this->getCacheFilePath());
367  }
368  }
369  /**
370  * try to load a class
371  *
372  * @param string $pClassName class name
373  *
374  * @return boolean
375  */
376  private function _loadClass($pClassName)
377  {
378  $className = strtolower($pClassName);
379  if (count($this->_classes) === 0) {
380  if (is_readable($this->getCacheFilePath())) {
381  //error_log('Loading classes from [' . $this->getCacheFilePath() . ']');
382  require $this->getCacheFilePath();
383  $this->_classes = $classes;
384  }
385  }
386  if (isset($this->_classes[$className])) {
387  require_once $this->_classes[$className];
388  //error_log('loaded class [' . $className . ']');
389  return true;
390  }
391  return false;
392  }
393  /**
394  * add a directory to autoloaded directories
395  *
396  * @param string $pDirectory directory path
397  * @param boolean $pRecursive should we recursively scan this directory
398  *
399  * @throws dcp\DirectoriesAutoloaderException
400  *
401  * @return dcp\DirectoriesAutoloader
402  */
403  public function addDirectory($pDirectory, $pRecursive = true)
404  {
405  if (!is_readable($pDirectory)) {
406  throw new DirectoriesAutoloaderException('Cannot read from [' . $pDirectory . ']');
407  }
408  $this->_directories[$pDirectory] = $pRecursive ? true : false;
409  return self::$_instance;
410  }
411  private $_directories = array();
412 }
414 {
415  /**
416  * Factory to decide which classHunter we use (should we use namespaces or not?)
417  *
418  * @param string $version php version
419  *
420  * @return \dcp\ClassHunterForPHP5_3|\dcp\ClassHunterForPHP5_2
421  */
422  public static function create($version)
423  {
424  return new ClassHunterForPHP5_3();
425  }
426 }
← centre documentaire © anakeen - published under CC License - Dynacase