1<?php
2/**
3 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @author   Jan Schneider <jan@horde.org>
9 * @author   Michael Slusarz <slusarz@horde.org>
10 * @category Horde
11 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
12 * @package  Autoloader_Cache
13 */
14
15require_once 'Horde/Autoloader/Default.php';
16require_once 'Horde/Autoloader/Cache/Bootstrap.php';
17
18/**
19 * Decorator for Horde_Autoloader that implements caching of class-file-maps.
20 *
21 * @author    Jan Schneider <jan@horde.org>
22 * @author    Michael Slusarz <slusarz@horde.org>
23 * @category  Horde
24 * @copyright 2011-2017 Horde LLC
25 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
26 * @package   Autoloader_Cache
27 */
28class Horde_Autoloader_Cache extends Horde_Autoloader_Default
29{
30    /* Cache types. @todo: Remove (not used) */
31    const APC = 1;
32    const XCACHE = 2;
33    const EACCELERATOR = 3;
34    const TEMPFILE = 4;
35
36    /* Cache key prefix. */
37    const PREFIX = 'horde_autoloader_cache';
38
39    /* Key that holds list of autoloader cache keys. */
40    const KEYLIST = 'horde_autoloader_keys';
41
42    /**
43     * Map of all classes already looked up.
44     *
45     * @var array
46     */
47    protected $_cache = array();
48
49    /**
50     * Cache key name.
51     *
52     * @var string
53     */
54    protected $_cachekey;
55
56    /**
57     * Has the cache changed since the last save?
58     *
59     * @var boolean
60     */
61    protected $_changed = false;
62
63    /**
64     * Is this a new key?
65     *
66     * @var boolean
67     */
68    protected $_newkey = false;
69
70    /**
71     */
72    protected $_storage;
73
74    /**
75     * Constructor.
76     */
77    public function __construct()
78    {
79        parent::__construct();
80
81        $key = isset($_SERVER['SERVER_NAME'])
82            ? $_SERVER['SERVER_NAME']
83            : '';
84        $key .= '|' . __FILE__;
85
86        $this->_cachekey = self::PREFIX . '_' . hash('md5', $key);
87        $this->_storage = new Horde_Autoloader_Cache_Bootstrap();
88
89        $data = $this->_storage->get($this->_cachekey);
90        if ($data === false) {
91            $this->_newkey = true;
92        } else {
93            $this->_cache = $data;
94        }
95
96        register_shutdown_function(array($this, 'shutdown'));
97    }
98
99    /**
100     * Shutdown method.
101     *
102     * Attempts to save the class map to the cache.
103     */
104    public function shutdown()
105    {
106        if (!$this->_changed) {
107            return;
108        }
109
110        if (!$this->_storage->set($this->_cachekey, $this->_cache)) {
111            error_log('Cannot write Autoloader cache to backend.', 4);
112        }
113
114        if ($this->_newkey) {
115            $keylist = $this->_getKeylist();
116            $keylist[] = $this->_cachekey;
117            $this->_saveKeylist($keylist);
118        }
119    }
120
121    /**
122     */
123    public function loadClass($className)
124    {
125        $exists = isset($this->_cache[$className]);
126
127        if (parent::loadClass($className)) {
128            return true;
129        }
130
131        /* Cache miss. Remove cache entry and try to load again. */
132        if ($exists) {
133            unset($this->_cache[$className]);
134            return parent::loadClass($className);
135        }
136
137        return false;
138    }
139
140    /**
141     * Search registered mappers in LIFO order.
142     *
143     * @param string $className  Classname.
144     *
145     * @return string  Path.
146     */
147    public function mapToPath($className)
148    {
149        if (isset($this->_cache[$className])) {
150            return $this->_cache[$className];
151        }
152
153        if ($res = parent::mapToPath($className)) {
154            $this->_cache[$className] = $res;
155            $this->_changed = true;
156        }
157
158        return $res;
159    }
160
161    /**
162     * Prunes the autoloader cache.
163     *
164     * @return boolean  True if pruning succeeded.
165     */
166    public function prune()
167    {
168        foreach (array_unique(array_merge($this->_getKeylist(), array($this->_cachekey))) as $val) {
169            $this->_storage->delete($val);
170        }
171
172        $this->_cache = array();
173
174        $this->_saveKeylist(array());
175
176        return true;
177    }
178
179    /**
180     * Returns the keylist.
181     *
182     * @return array  Keylist.
183     */
184    protected function _getKeylist()
185    {
186        $keylist = $this->_storage->get(self::KEYLIST);
187
188        return empty($keylist)
189            ? array()
190            : $keylist;
191    }
192
193    /**
194     * Saves the keylist.
195     *
196     * @param array $keylist  Keylist to save.
197     */
198    protected function _saveKeylist($keylist)
199    {
200        $keylist = $this->_storage->set(self::KEYLIST, $keylist);
201    }
202
203}
204
205spl_autoload_unregister(array($__autoloader, 'loadClass'));
206$__autoloader = new Horde_Autoloader_Cache();
207$__autoloader->registerAutoloader();
208