Simpletest Coverage - includes/registry.inc

1 <?php
2 // $Id: registry.inc,v 1.20 2009/08/04 05:09:40 webchick Exp $
3
4 /**
5 * @file
6 * This file contains the code registry parser engine.
7 */
8
9 /**
10 * @defgroup registry Code registry
11 * @{
12 * The code registry engine.
13 *
14 * Drupal maintains an internal registry of all functions or classes in the
15 * system, allowing it to lazy-load code files as needed (reducing the amount
16 * of code that must be parsed on each request).
17 */
18
19 /**
20 * @see registry_rebuild.
21 */
22 function _registry_rebuild() {
23
24 // The registry serves as a central autoloader for all classes, including
25 // the database query builders. However, the registry rebuild process
26 // requires write ability to the database, which means having access to the
27 // query builders that require the registry in order to be loaded. That
28 // causes a fatal race condition. Therefore we manually include the
29 // appropriate query builders for the currently active database before the
30 // registry rebuild process runs.
31 $connection_info = Database::getConnectionInfo();
32 $driver = $connection_info['default']['driver'];
33 require_once DRUPAL_ROOT . '/includes/database/query.inc';
34 require_once DRUPAL_ROOT . '/includes/database/select.inc';
35 require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/query.inc';
36
37 // Reset the resources cache.
38 _registry_get_resource_name();
39
40 // Get current list of modules and their files.
41 $modules = system_get_module_data();
42 // Get the list of files we are going to parse.
43 $files = array();
44 foreach ($modules as &$module) {
45 $dir = dirname($module->filepath);
46
47 // Store the module directory for use in hook_registry_files_alter().
48 $module->dir = $dir;
49
50 if ($module->status) {
51 // Add files for enabled modules to the registry.
52 foreach ($module->info['files'] as $file) {
53 $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
54 }
55 }
56 }
57 foreach (file_scan_directory('includes', '/\.inc$/') as $filename => $file) {
58 $files["$filename"] = array('module' => '', 'weight' => 0);
59 }
60
61 // Allow modules to manually modify the list of files before the registry
62 // parses them. The $modules array provides the .info file information, which
63 // includes the list of files registered to each module. Any files in the
64 // list can then be added to the list of files that the registry will parse,
65 // or modify attributes of a file.
66 drupal_alter('registry_files', $files, $modules);
67 foreach (registry_get_parsed_files() as $filename => $file) {
68 // Add the file creation and modification dates to those files we have
69 // already parsed.
70 if (isset($files[$filename])) {
71 $files[$filename]['filectime'] = $file['filectime'];
72 $files[$filename]['filemtime'] = $file['filemtime'];
73 }
74 else {
75 // Flush the registry of resources in files that are no longer on disc
76 // or are in files that no installed modules require to be parsed.
77 db_delete('registry')
78 ->condition('filename', $filename)
79 ->execute();
80 db_delete('registry_file')
81 ->condition('filename', $filename)
82 ->execute();
83 }
84 }
85 $parsed_files = _registry_parse_files($files);
86
87 $unchanged_resources = array();
88 $lookup_cache = array();
89 if ($cache = cache_get('lookup_cache', 'cache_registry')) {
90 $lookup_cache = $cache->data;
91 }
92 foreach ($lookup_cache as $key => $file) {
93 // If the file for this cached resource is carried over unchanged from
94 // the last registry build, then we can safely re-cache it.
95 if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) {
96 $unchanged_resources[$key] = $file;
97 }
98 }
99 _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
100
101 module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
102 cache_clear_all('*', 'cache_registry', TRUE);
103
104 // We have some unchanged resources, warm up the cache - no need to pay
105 // for looking them up again.
106 if (count($unchanged_resources) > 0) {
107 cache_set('lookup_cache', $unchanged_resources, 'cache_registry');
108 }
109 }
110
111 /**
112 * Return the list of files in registry_file
113 */
114 function registry_get_parsed_files() {
115 $files = array();
116 // We want the result as a keyed array.
117 $files = db_query("SELECT * FROM {registry_file}")->fetchAllAssoc('filename', PDO::FETCH_ASSOC);
118 return $files;
119 }
120
121 /**
122 * Parse all files that have changed since the registry was last built, and save their function and class listings.
123 *
124 * @param $files
125 * The list of files to check and parse.
126 */
127 function _registry_parse_files($files) {
128 $parsed_files = array();
129 foreach ($files as $filename => $file) {
130 $filectime = filectime($filename);
131 $filemtime = filemtime($filename);
132 $modified_file = !isset($file['filectime']) || !isset($file['filemtime'])
133 || $filectime != $file['filectime'] || $filemtime != $file['filemtime'];
134 if ($modified_file) {
135 $contents = file_get_contents($filename);
136 $parsed_files[] = $filename;
137 // We update the filectime/filemtime after we've saved the files resources
138 // rather than here, so if we don't make it through this rebuild, the next
139 // run will reparse the file.
140 _registry_parse_file($filename, $contents, $file['module'], $file['weight']);
141 db_merge('registry_file')
142 ->key(array('filename' => $filename))
143 ->fields(array(
144 'filectime' => $filectime,
145 'filemtime' => $filemtime,
146 ))
147 ->execute();
148 }
149 }
150 return $parsed_files;
151 }
152
153 /**
154 * Parse a file and save its function and class listings.
155 *
156 * @param $filename
157 * Name of the file we are going to parse.
158 * @param $contents
159 * Contents of the file we are going to parse as a string.
160 * @param $module
161 * (optional) Name of the module this file belongs to.
162 * @param $weight
163 * (optional) Weight of the module.
164 */
165 function _registry_parse_file($filename, $contents, $module = '', $weight = 0) {
166 $map = &drupal_static(__FUNCTION__, array(T_FUNCTION => 'function', T_CLASS => 'class', T_INTERFACE => 'interface'));
167 // Delete registry entries for this file, so we can insert the new resources.
168 db_delete('registry')
169 ->condition('filename', $filename)
170 ->execute();
171 $tokens = token_get_all($contents);
172 while ($token = next($tokens)) {
173 // Ignore all tokens except for those we are specifically saving.
174 if (is_array($token) && isset($map[$token[0]])) {
175 $type = $map[$token[0]];
176 if ($resource_name = _registry_get_resource_name($tokens, $type)) {
177 $suffix = '';
178 // Collect the part of the function name after the module name,
179 // so that we can query the registry for possible hook implementations.
180 if ($type == 'function' && !empty($module)) {
181 $n = strlen($module);
182 if (substr($resource_name, 0, $n) == $module) {
183 $suffix = substr($resource_name, $n + 1);
184 }
185 }
186 $fields = array(
187 'filename' => $filename,
188 'module' => $module,
189 'suffix' => $suffix,
190 'weight' => $weight,
191 );
192 // Because some systems, such as cache, currently use duplicate function
193 // names in separate files an insert query cannot be used here as it
194 // would cause a key constraint violation. Instead we use a merge query.
195 // In practice this should not be an issue as those systems all initialize
196 // pre-registry and therefore are never loaded by the registry so it
197 // doesn't matter if those records in the registry table point to one
198 // filename instead of another.
199 // TODO: Convert this back to an insert query after all duplicate
200 // function names have been purged from Drupal.
201 db_merge('registry')
202 ->key(array('name' => $resource_name, 'type' => $type))
203 ->fields($fields)
204 ->execute();
205
206 // We skip the body because classes may contain functions.
207 _registry_skip_body($tokens);
208 }
209 }
210 }
211 }
212
213 /**
214 * Derive the name of the next resource in the token stream.
215 *
216 * When called without arguments, it resets its static cache.
217 *
218 * @param $tokens
219 * The collection of tokens for the current file being parsed.
220 * @param $type
221 * The human-readable token name, either: "function", "class", or "interface".
222 * @return
223 * The name of the resource, or FALSE if the resource has already been processed.
224 */
225 function _registry_get_resource_name(&$tokens = NULL, $type = NULL) {
226 // Keep a running list of all resources we've saved so far, so that we never
227 // save one more than once.
228 $resources = &drupal_static(__FUNCTION__);
229
230 if (!isset($tokens)) {
231 $resources = array();
232 return;
233 }
234 // Determine the name of the resource.
235 next($tokens); // Eat a space.
236 $token = next($tokens);
237 if ($token == '&') {
238 $token = next($tokens);
239 }
240 $resource_name = $token[1];
241
242 // Ensure that we never save it more than once.
243 if (isset($resources[$type][$resource_name])) {
244 return FALSE;
245 }
246 $resources[$type][$resource_name] = TRUE;
247
248 return $resource_name;
249 }
250
251 /**
252 * Skip the body of a code block, as defined by { and }.
253 *
254 * This function assumes that the body starts at the next instance
255 * of { from the current position.
256 *
257 * @param $tokens
258 */
259 function _registry_skip_body(&$tokens) {
260 $num_braces = 1;
261
262 $token = '';
263 // Get to the first open brace.
264 while ($token != '{' && ($token = next($tokens)));
265
266 // Scan through the rest of the tokens until we reach the matching
267 // end brace.
268 while ($num_braces && ($token = next($tokens))) {
269 // PHP is really logical to have three different tokens for { with
270 // inconsistent names and only one for a closing brace.
271 if ($token == '{' || (is_array($token) && ($token[0] == T_DOLLAR_OPEN_CURLY_BRACES || $token[0] == T_CURLY_OPEN))) {
272 ++$num_braces;
273 }
274 elseif ($token == '}') {
275 --$num_braces;
276 }
277 // Consume strings manually as workaround for a bug in PHP < 5.2.3 (see
278 // http://drupal.org/node/368116).
279 elseif ($token == '"' || $token == '`' || (is_array($token) && $token[0] == T_START_HEREDOC)) {
280 $stop = is_array($token) ? T_END_HEREDOC : $token;
281 while (($token = next($tokens)) && (is_array($token) ? $token[0] : $token) != $stop);
282 }
283 }
284 }
285
286 /**
287 * @} End of "defgroup registry".
288 */
289
290

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.