protected function collect_descriptors($files)
 {
     $descriptors = [];
     foreach ($files as $file) {
         $descriptors = array_merge($descriptors, self::parse_file($file));
     }
     \ICanBoogie\stable_sort($descriptors, function ($v) {
         return $v->normalized_id;
     });
     return $descriptors;
 }
 /**
  * Sorts routes according to their type and computed weight.
  *
  * Routes and grouped in two groups: static routes and dynamic routes. The difference between
  * static and dynamic routes is that dynamic routes capture parameters from the path and thus
  * require a regex to compute the match, whereas static routes only require is simple string
  * comparison.
  *
  * Dynamic routes are ordered according to their weight, which is computed from the number
  * of static parts before the first capture. The more static parts, the lighter the route is.
  *
  * @return array An array with the static routes and dynamic routes.
  */
 private function sort_routes()
 {
     if ($this->static !== null) {
         return [$this->static, $this->dynamic];
     }
     $static = [];
     $dynamic = [];
     $weights = [];
     foreach ($this->routes as $id => $definition) {
         $pattern = $definition[RouteDefinition::PATTERN];
         $first_capture_position = strpos($pattern, ':') ?: strpos($pattern, '<');
         if ($first_capture_position === false) {
             $static[$id] = $definition;
         } else {
             $dynamic[$id] = $definition;
             $weights[$id] = substr_count($pattern, '/', 0, $first_capture_position);
         }
     }
     \ICanBoogie\stable_sort($dynamic, function ($v, $k) use($weights) {
         return -$weights[$k];
     });
     $this->static = $static;
     $this->dynamic = $dynamic;
     return [$static, $dynamic];
 }
 /**
  * Orders the module ids provided according to module inheritance and weight.
  *
  * @param array $ids The module ids to order.
  * @param array $descriptors Module descriptors.
  *
  * @return array
  */
 public function order_ids(array $ids, array $descriptors = null)
 {
     $ordered = [];
     $extends_weight = [];
     if ($descriptors === null) {
         $descriptors = $this->descriptors;
     }
     $count_extends = function ($super_id) use(&$count_extends, &$descriptors) {
         $i = 0;
         foreach ($descriptors as $id => $descriptor) {
             if ($descriptor[Descriptor::INHERITS] !== $super_id) {
                 continue;
             }
             $i += 1 + $count_extends($id);
         }
         return $i;
     };
     $count_required = function ($required_id) use(&$descriptors, &$extends_weight) {
         $i = 0;
         foreach ($descriptors as $id => $descriptor) {
             if (!in_array($required_id, $descriptor[Descriptor::REQUIRES])) {
                 continue;
             }
             $i += 1 + $extends_weight[$id];
         }
         return $i;
     };
     foreach ($ids as $id) {
         $extends_weight[$id] = $count_extends($id);
     }
     foreach ($ids as $id) {
         $ordered[$id] = -$extends_weight[$id] - $count_required($id) + $descriptors[$id][Descriptor::WEIGHT];
     }
     \ICanBoogie\stable_sort($ordered);
     return array_keys($ordered);
 }