Code coverage for /20081101/includes/registry.inc

Line #Times calledCode
1
<?php
2
// $Id: registry.inc,v 1.7 2008/10/31 02:18:22 dries 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). The list of included files
is
17
 * cached per menu callback for subsequent loading by the menu router. This
way,
18
 * a given page request will have all the code it needs but little else,
minimizing
19
 * time spent parsing unneeded code.
20
 */
21
22
/**
23
 * @see registry_rebuild.
24
 */
25146
function _registry_rebuild() {
26
27
  // The registry serves as a central autoloader for all classes, including
28
  // the database query builders.  However, the registry rebuild process
29
  // requires write ability to the database, which means having access to
the
30
  // query builders that require the registry in order to be loaded.  That
31
  // causes a fatal race condition.  Therefore we manually include the
32
  // appropriate query builders for the currently active database before
the
33
  // registry rebuild process runs.
34146
  $connection_info = Database::getConnectionInfo();
35146
  $driver = $connection_info['default']['driver'];
36146
  require_once DRUPAL_ROOT . '/includes/database/query.inc';
37146
  require_once DRUPAL_ROOT . '/includes/database/select.inc';
38146
  require_once DRUPAL_ROOT . '/includes/database/' . $driver .
'/query.inc';
39
40
  // Reset the resources cache.
41146
  _registry_get_resource_name();
42
  // Get the list of files we are going to parse.
43146
  $files = array();
44146
  foreach (module_rebuild_cache() as $module) {
45146
    if ($module->status) {
46146
      $dir = dirname($module->filename);
47146
      foreach ($module->info['files'] as $file) {
48146
        $files["$dir/$file"] = array('module' => $module->name, 'weight' =>
$module->weight);
49146
      }
50146
    }
51146
  }
52146
  foreach (file_scan_directory('includes', '/\.inc$/') as $filename =>
$file) {
53146
    $files["$filename"] = array('module' => '', 'weight' => 0);
54146
  }
55
56146
  foreach (registry_get_parsed_files() as $filename => $file) {
57
    // Add the md5 to those files we've already parsed.
5812
    if (isset($files[$filename])) {
5912
      $files[$filename]['md5'] = $file['md5'];
6012
    }
61
    else {
62
      // Flush the registry of resources in files that are no longer on
disc
63
      // or don't belong to installed modules.
641
      db_delete('registry')
651
        ->condition('filename', $filename)
661
        ->execute();
671
      db_delete('registry_file')
681
        ->condition('filename', $filename)
691
        ->execute();
70
    }
7112
  }
72146
  _registry_parse_files($files);
73
74146
  module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
75146
  cache_clear_all('*', 'cache_registry', TRUE);
76146
}
77
78
/**
79
 * Return the list of files in registry_file
80
 */
81146
function registry_get_parsed_files() {
82146
  $files = array();
83
  // We want the result as a keyed array.
84146
  $files = db_query("SELECT * FROM
{registry_file}")->fetchAllAssoc('filename', PDO::FETCH_ASSOC);
85146
  return $files;
860
}
87
88
/**
89
 * Parse all files that have changed since the registry was last built, and
save their function and class listings.
90
 *
91
 * @param $files
92
 *  The list of files to check and parse.
93
 */
94146
function _registry_parse_files($files) {
95146
  $changed_files = array();
96146
  foreach ($files as $filename => $file) {
97146
    $contents = file_get_contents($filename);
98146
    $md5 = md5($contents);
99146
    $new_file = !isset($file['md5']);
100146
    if ($new_file || $md5 != $file['md5']) {
101
      // We update the md5 after we've saved the files resources rather
than here, so if we
102
      // don't make it through this rebuild, the next run will reparse the
file.
103136
      _registry_parse_file($filename, $contents, $file['module'],
$file['weight']);
104136
      $file['md5'] = $md5;
105136
      db_merge('registry_file')
106136
        ->key(array('filename' => $filename))
107136
        ->fields(array('md5' => $md5))
108136
        ->execute();
109136
    }
110146
  }
111146
}
112
113
/**
114
 * Parse a file and save its function and class listings.
115
 *
116
 * @param $filename
117
 *  Name of the file we are going to parse.
118
 * @param $contents
119
 *  Contents of the file we are going to parse as a string.
120
 * @param $module
121
 *   (optional) Name of the module this file belongs to.
122
 * @param $weight
123
 *   (optional) Weight of the module.
124
 */
125146
function _registry_parse_file($filename, $contents, $module = '', $weight =
0) {
126136
  static $map = array(T_FUNCTION => 'function', T_CLASS => 'class',
T_INTERFACE => 'interface');
127
  // Delete registry entries for this file, so we can insert the new
resources.
128136
  db_delete('registry')
129136
    ->condition('filename', $filename)
130136
    ->execute();
131136
  $tokens = token_get_all($contents);
132136
  while ($token = next($tokens)) {
133
    // Ignore all tokens except for those we are specifically saving.
134136
    if (is_array($token) && isset($map[$token[0]])) {
135136
      $type = $map[$token[0]];
136136
      if ($resource_name = _registry_get_resource_name($tokens, $type)) {
137136
        $suffix = '';
138
        // Collect the part of the function name after the module name,
139
        // so that we can query the registry for possible hook
implementations.
140136
        if ($type == 'function' && !empty($module)) {
141136
          $n = strlen($module);
142136
          if (substr($resource_name, 0, $n) == $module) {
143136
            $suffix = substr($resource_name, $n + 1);
144136
          }
145136
        }
146
        $fields = array(
147136
          'filename' => $filename,
148136
          'module' => $module,
149136
          'suffix' => $suffix,
150136
          'weight' => $weight,
151136
        );
152
        // Because some systems, such as cache, currently use duplicate
function
153
        // names in separate files an insert query cannot be used here as
it
154
        // would cause a key constraint violation.  Instead we use a merge
query.
155
        // In practice this should not be an issue as those systems all
initialize
156
        // pre-registry and therefore are never loaded by the registry so
it
157
        // doesn't matter if those records in the registry table point to
one
158
        // filename instead of another.
159
        // TODO: Convert this back to an insert query after all duplicate
160
        // function names have been purged from Drupal.
161136
        db_merge('registry')
162136
          ->key(array('name' => $resource_name, 'type' => $type))
163136
          ->fields($fields)
164136
          ->execute();
165
166
        // We skip the body because classes may contain functions.
167136
        _registry_skip_body($tokens);
168136
      }
169136
    }
170136
  }
171136
}
172
173
/**
174
 * Derive the name of the next resource in the token stream.
175
 *
176
 * When called without arguments, it resets its static cache.
177
 *
178
 * @param $tokens
179
 *  The collection of tokens for the current file being parsed.
180
 * @param $type
181
 *  The human-readable token name, either: "function", "class", or
"interface".
182
 * @return
183
 *  The name of the resource, or FALSE if the resource has already been
processed.
184
 */
185146
function _registry_get_resource_name(&$tokens = NULL, $type = NULL) {
186
  // Keep a running list of all resources we've saved so far, so that we
never
187
  // save one more than once.
188146
  static $resources;
189
190146
  if (!isset($tokens)) {
191146
    $resources = array();
192146
    return;
1930
  }
194
  // Determine the name of the resource.
195136
  next($tokens); // Eat a space.
196136
  $token = next($tokens);
197136
  if ($token == '&') {
198134
    $token = next($tokens);
199134
  }
200136
  $resource_name = $token[1];
201
202
  // Ensure that we never save it more than once.
203136
  if (isset($resources[$type][$resource_name])) {
204134
    return FALSE;
2050
  }
206136
  $resources[$type][$resource_name] = TRUE;
207
208136
  return $resource_name;
2090
}
210
211
/**
212
 * Skip the body of a code block, as defined by { and }.
213
 *
214
 * This function assumes that the body starts at the next instance
215
 * of { from the current position.
216
 *
217
 * @param $tokens
218
 */
219146
function _registry_skip_body(&$tokens) {
220136
  $num_braces = 1;
221
222136
  $token = '';
223
  // Get to the first open brace.
224136
  while ($token != '{' && ($token = next($tokens)));
225
226
  // Scan through the rest of the tokens until we reach the matching
227
  // end brace.
228136
  while ($num_braces && ($token = next($tokens))) {
229136
    if ($token == '{') {
230136
      ++$num_braces;
231136
    }
232136
    elseif ($token == '}') {
233136
      --$num_braces;
234136
    }
235136
  }
236136
}
237
238
/**
239
 * @} End of "defgroup registry".
240
 */
241
242146