/** * Extends the list of sources to compile * * When you modify a file, all these classes may have their matching mysql structure changed : * - the class itself * - all classes that extend the class or use the trait * * @param &$sources Reflection_Source[] * @return Reflection_Source[] added sources list */ public function moreSourcesToCompile(&$sources) { $added = []; // Builder is disabled during the listing as we want to get the original linked class name when // reading class annotation @link Builder::current()->enabled = false; /** @var $search Dependency */ $search = Search_Object::create(Dependency::class); $search->file_name = Func::notLike('cache/%'); $search->type = Func::orOp([Dependency::T_EXTENDS, Dependency::T_USE]); foreach ($sources as $source) { foreach ($source->getClasses() as $class) { while ($linked_class = $class->getAnnotation('link')->value) { $source = Reflection_Class::of($linked_class)->source; if (!isset($sources[$source->file_name])) { $sources[$source->file_name] = $source; $added[$source->file_name] = $source; } $class = $source->getClass($linked_class); } $search->dependency_name = $class->name; foreach (Dao::search($search, Dependency::class) as $dependency) { /** @var $dependency Dependency */ if (!isset($sources[$dependency->file_name])) { $source = new Reflection_Source($dependency->file_name); $sources[$dependency->file_name] = $source; $added[$dependency->file_name] = $source; } } } } Builder::current()->enabled = true; return $added; }
/** * @param $advice string[]|object[]|string * @param $advice_class_name string * @param $advice_method_name string * @param $advice_function_name string * @param $advice_parameters_string string * @param $advice_has_return boolean * @param $is_advice_static boolean * @param $joinpoint_code string * @param $i2 string * @param $result string * @return string */ private function generateAdviceCode($advice, $advice_class_name, $advice_method_name, $advice_function_name, $advice_parameters_string, $advice_has_return, $is_advice_static, $joinpoint_code, $i2, $result) { // $advice_code if (is_array($advice)) { $methods_flags = [Reflection_Class::T_DOC_EXTENDS, T_EXTENDS, T_USE]; $method = $advice[0] == '$this' ? $this->class->getMethods($methods_flags)[$advice_method_name] : Reflection_Method::of($advice_class_name, $advice_method_name, $methods_flags); $ref = $method->returnsReference() ? '&' : ''; // static method call if ($is_advice_static) { return $joinpoint_code . $i2 . ($advice_has_return ? $result . SP . '=' . $ref . SP : '') . (in_array($advice[0], ['self', 'static']) ? $advice[0] : BS . $advice_class_name) . '::' . $advice_method_name . '(' . $advice_parameters_string . ');'; } elseif ($advice[0] == '$this') { return $joinpoint_code . $i2 . ($advice_has_return ? $result . SP . '=' . $ref . SP : '') . '$this->' . $advice_method_name . '(' . $advice_parameters_string . ');'; } else { return $i2 . '/** @var $object_ ' . BS . $advice_class_name . ' */' . $i2 . '$object_ = \\SAF\\Framework\\Session::current()->plugins->get(' . "'{$advice_class_name}'" . ');' . $joinpoint_code . $i2 . ($advice_has_return ? 'if ($object_) ' . $result . SP . '=' . $ref . SP : '') . '$object_->' . $advice_method_name . '(' . $advice_parameters_string . ');'; } } else { $ref = (new ReflectionFunction($advice_function_name))->returnsReference() ? '&' : ''; return $joinpoint_code . $i2 . ($advice_has_return ? $result . SP . '=' . $ref . SP : '') . $advice_function_name . '(' . $advice_parameters_string . ');'; } }
/** * @param $class_name string The base class name * @param $interfaces_traits string[] The interfaces and traits names list * @param $get_source boolean if true, get built [$name, $source] instead of $name * @return string|string[] the full name of the built class */ public static function build($class_name, $interfaces_traits = [], $get_source = false) { $key = join(DOT, $interfaces_traits); if (isset(self::$builds[$class_name][$key])) { return self::$builds[$class_name][$key]; } else { $interfaces = []; $traits = []; foreach ($interfaces_traits as $interface_trait) { $class = Reflection_Class::of($interface_trait); if ($class->isInterface()) { $interfaces[$interface_trait] = $interface_trait; } elseif ($class->isTrait()) { foreach ($class->getListAnnotation('implements')->values() as $implements) { $interfaces[$implements] = $implements; } $level = 0; foreach ($class->getListAnnotation('extends')->values() as $extends) { if (Dao::search(['class_name' => $extends, 'declaration' => Dependency::T_TRAIT_DECLARATION], Dependency::class)) { foreach ($traits as $trait_level => $trait_names) { if (isset($trait_names[$extends])) { $level = max($level, $trait_level + 1); } } } } $traits[$level][$interface_trait] = $interface_trait; } else { trigger_error('Unknown interface/trait ' . DQ . $interface_trait . DQ . ' while building ' . $class_name, E_USER_ERROR); } } $built_class = self::buildClass($class_name, $interfaces, $traits, $get_source); if (!$get_source) { self::$builds[$class_name][$key] = $built_class; } return $built_class; } }
/** * @param $properties array * @param $class Reflection_Class */ private function scanForImplements(&$properties, Reflection_Class $class) { // properties from the class and its direct traits $implemented_properties = $class->getProperties([T_USE]); foreach ($implemented_properties as $property) { $expr = '%' . '\\n\\s+\\*\\s+' . '@(getter|link|setter)' . '(?:\\s+' . '(?:([\\\\\\w]+)::)?' . '(\\w+)' . ')?' . '%'; preg_match_all($expr, $property->getDocComment(), $match); foreach ($match[1] as $type) { $type = $type == 'setter' ? 'write' : 'read'; $properties[$property->name]['implements'][$type] = true; } if ($property->getParent()) { $properties[$property->name]['override'] = true; } } // properties overridden into the class and its direct traits $documentations = $class->getDocComment([T_USE]); foreach ($this->scanForOverrides($documentations, ['getter', 'link', 'setter']) as $match) { $properties[$match['property_name']]['implements'][$match['type']] = true; if (!isset($implemented_properties[$match['property_name']])) { $class_properties = $class->getProperties([T_EXTENDS]); $extends = $class; while (!isset($class_properties[$match['property_name']])) { // TODO try the error to have a class with @representative and property names that do not exist. That will crash here but the error message is incomprehensible $extends = $extends->source->getOutsideClass($extends->getListAnnotation('extends')->values()[0]); $class_properties = $extends->getProperties([T_EXTENDS]); } $property = $class_properties[$match['property_name']]; if (isset($extends)) { $property->final_class = $class->name; } if (!strpos($property->getDocComment(), '@getter') && !strpos($property->getDocComment(), '@link') && !strpos($property->getDocComment(), '@setter')) { $expr = '%@override\\s+' . $match['property_name'] . '\\s+.*(@getter|@link|@setter)%'; preg_match($expr, $property->class->getDocComment(), $match2); if ($match2) { $properties[$match['property_name']]['override'] = true; } } } } }
/** * Gets a single or multiple class type as its Reflection_Class * * @param $reflection_class_name string Any reflection class name that implements Reflection_Class * @return Interfaces\Reflection_Class */ public function asReflectionClass($reflection_class_name = null) { if ($reflection_class_name) { return is_a($reflection_class_name, PHP\Reflection_Class::class, true) ? PHP\Reflection_Class::of($this->getElementTypeAsString()) : (new Reflection_Class($reflection_class_name))->newInstance($this->getElementTypeAsString()); } return new Reflection_Class($this->getElementTypeAsString()); }
/** * @param $doc_comment string * @param $annotation_name string * @param $i integer * @param $annotation_class string * @param $reflection_object Has_Doc_Comment|Reflection * @return Annotation */ private static function parseAnnotationValue($doc_comment, $annotation_name, &$i, $annotation_class, Reflection $reflection_object) { $i += strlen($annotation_name) + 1; $next_char = $doc_comment[$i]; switch ($next_char) { case SP: case TAB: $i++; $j = strlen($doc_comment); $next_annotation = strpos($doc_comment, SP . '* @', $i); $end_doc_comment = strpos($doc_comment, SP . '*/', $i); $next_in = strpos($doc_comment, LF . self::DOC_COMMENT_IN, $i); if ($next_annotation !== false && $next_annotation < $j) { $j = $next_annotation; } if ($end_doc_comment !== false && $end_doc_comment < $j) { $j = $end_doc_comment; } if ($next_in !== false && $next_in < $j) { $j = $next_in; } if ($j === false) { trigger_error('Missing doc_comment end', E_USER_ERROR); } $value = trim(preg_replace('%\\s*\\n\\s+\\*\\s*%', '', substr($doc_comment, $i, $j - $i))); break; case CR: case LF: $value = true; break; default: $value = null; } /** @var $annotation Annotation */ $annotation = isset($value) ? new $annotation_class($value, $reflection_object, $annotation_name) : null; if (isset($annotation) && isA($annotation, Annotation_In::class)) { /** @var $annotation Annotation_In */ $j = strrpos(substr($doc_comment, 0, $i), LF . self::DOC_COMMENT_IN); if ($j === false) { $annotation->class_name = $reflection_object instanceof Reflection_Class_Component ? $reflection_object->getDeclaringClassName() : $reflection_object->getName(); } else { $j += strlen(self::DOC_COMMENT_IN) + 1; $k = strpos($doc_comment, LF, $j); $annotation->class_name = substr($doc_comment, $j, $k - $j); } } if (isset($annotation) && isA($annotation, Types_Annotation::class)) { $do = false; if (is_array($annotation->value)) { foreach ($annotation->value as $value) { if ($value && (ctype_upper($value[0]) || $value[0] == BS)) { $do = true; break; } } } else { $do = $annotation->value && ctype_upper($annotation->value[0]); } if ($do) { /** @var $annotation Types_Annotation */ $j = strrpos(substr($doc_comment, 0, $i), LF . self::DOC_COMMENT_IN); if ($j === false) { $class_name = $reflection_object instanceof Reflection_Class_Component ? $reflection_object->getDeclaringClassName() : $reflection_object->getName(); $namespace = Namespaces::of($class_name); $use = PHP\Reflection_Class::of($class_name)->getNamespaceUse(); } else { $j += strlen(self::DOC_COMMENT_IN) + 1; $k = strpos($doc_comment, LF, $j); $in_class = substr($doc_comment, $j, $k - $j); $namespace = Namespaces::of($in_class); $use = PHP\Reflection_Class::of($in_class)->getNamespaceUse(); } $annotation->applyNamespace($namespace, $use); } elseif (is_array($annotation->value)) { foreach ($annotation->value as $key => $value) { $annotation->value[$key] = Builder::className($value); } } else { if ($annotation->value[0] === BS) { $annotation->value = substr($annotation->value, 1); } $annotation->value = Builder::className($annotation->value); } } return $annotation; }
/** * @param $property Reflection_Property * @return Reflection_Class */ private function getForeignClass(Reflection_Property $property) { $type = $property->getType(); $foreign_class_name = Builder::className($type->getElementTypeAsString()); if ($property instanceof PHP\Reflection_Property) { $foreign_class = PHP\Reflection_Class::of($foreign_class_name); } else { $reflection_class = new Reflection\Reflection_Class(get_class($property->getDeclaringClass())); $foreign_class = $reflection_class->newInstance($foreign_class_name); } return $foreign_class; }
/** * TODO property you'd better do this into the last @override field @$annotation_name class * * @param $class_property Reflection * @param $type_annotation Type_Annotation * @param $value string * @param $pos integer */ private function searchIntoFinalClass(Reflection $class_property, Type_Annotation $type_annotation, $value, $pos) { $class = $class_property instanceof Reflection_Property ? $class_property->getFinalClass() : $class_property; trigger_error(sprintf('Looking namespace use for Method_Annotation into final class %1 for property %2' . ' is not reliable', $class->getName(), $class_property->getName()), E_USER_WARNING); $php_class = Reflection_Class::of($class->getName()); $type_annotation->value = substr($value, 0, $pos); $type_annotation->applyNamespace($class->getNamespaceName(), $php_class->getNamespaceUse()); }
/** * @param $class_name string * @param $property_name string * @param $flags integer[] T_EXTENDS, T_USE * @return Reflection_Method */ public static function of($class_name, $property_name, $flags = []) { $properties = Reflection_Class::of($class_name)->getProperties($flags); return isset($properties[$property_name]) ? $properties[$property_name] : null; }
/** * Gets the classes that are into @extends instead of use to allow diamond multiple inheritance * * @return Reflection_Class[] */ public function getDocExtends() { $extends = []; $expr = '%' . '\\n\\s+\\*\\s+' . '@extends' . '\\s+([\\\\\\w]+)' . '%'; if (preg_match_all($expr, $this->getDocComment(), $matches)) { foreach ($matches[1] as $match) { $extends[] = Reflection_Class::of($this->fullClassName($match)); } } return $extends; }
/** * @param $class_name string * @param $method_name string * @param $flags integer[] T_EXTENDS, T_IMPLEMENTS, T_USE * @return Reflection_Method */ public static function of($class_name, $method_name, $flags = []) { $class = Reflection_Class::of($class_name); $methods = $class->getMethods($flags); if (!isset($methods[$method_name]) && in_array(T_EXTENDS, $flags)) { do { $class = $class->source->getOutsideClass($class->getListAnnotation('extends')->values()[0]); $methods = $class->getMethods($flags); } while (!isset($methods[$method_name])); } return $methods[$method_name]; }
/** * @param $properties array * @param $class Reflection_Class */ private function scanForSetters(&$properties, Reflection_Class $class) { foreach ($class->getProperties() as $property) { $expr = '%' . '\\n\\s+\\*\\s+' . '@setter' . '(?:\\s+(?:([\\\\\\w]+)::)?' . '(\\w+)?)?' . '%'; preg_match($expr, $property->getDocComment(), $match); if ($match) { $advice = [empty($match[1]) ? '$this' : $class->source->fullClassName($match[1]), empty($match[2]) ? Names::propertyToMethod($property->name, 'set') : $match[2]]; $properties[$property->name][] = ['write', $advice]; } } foreach ($this->scanForOverrides($class->getDocComment(), ['setter']) as $match) { $advice = [empty($match['class_name']) ? '$this' : $match['class_name'], empty($match['method_name']) ? Names::propertyToMethod($match['property_name'], 'set') : $match['method_name']]; $properties[$match['property_name']][] = ['write', $advice]; } }