/** * @return string */ function masterColumn() { if (!isset($this->master_column)) { $this->master_column = 'id_' . Names::setToSingle($this->property->getAnnotation('foreign')->value); } return $this->master_column; }
/** * @return string[] */ protected function getProperties() { // gets all properties from collection element class $class = new Reflection_Class($this->class_name); $properties = $class->getProperties([T_EXTENDS, T_USE]); // remove replaced properties /** @var $properties Reflection_Property[] */ $properties = Replaces_Annotations::removeReplacedProperties($properties); // remove linked class properties $linked_class = $class->getAnnotation('link')->value; if ($linked_class) { foreach (array_keys((new Reflection_Class($linked_class))->getProperties([T_EXTENDS, T_USE])) as $property_name) { unset($properties[$property_name]); } } // remove composite property $property_name = $this->property->getAnnotation('foreign')->value; if (isset($properties[$property_name])) { unset($properties[$property_name]); } // remove static and user-invisible properties foreach ($properties as $property_name => $property) { if ($property->isStatic() || $property->getListAnnotation('user')->has(User_Annotation::INVISIBLE)) { unset($properties[$property_name]); } } // returns properties return $properties; }
/** * Returns the value with string format * * @param $value string * @return string */ protected function formatString($value) { if ($this->property->getAnnotation('password')->value) { $value = strlen($value) ? str_repeat('*', strlen(Password::UNCHANGED)) : ''; } return $value; }
/** * @param $property Reflection_Property * @param $value mixed * @return Line[] */ private static function checkProperty(Reflection_Property $property, $value) { $report_lines = []; if (is_null($value)) { if (!$property->getAnnotation('null')) { self::checkValue($report_lines, false, $property, new Null_Annotation(false, $property), $value); } } else { foreach ($property->getAnnotations() as $annotation_name => $annotation) { switch ($annotation_name) { case 'min_length': self::checkValue($report_lines, strlen($value) >= $annotation->value, $property, $annotation, $value); break; case 'max_length': self::checkValue($report_lines, strlen($value) <= $annotation->value, $property, $annotation, $value); break; case 'min_value': self::checkValue($report_lines, $value >= $annotation->value, $property, $annotation, $value); break; case 'max_value': self::checkValue($report_lines, $value <= $annotation->value, $property, $annotation, $value); break; //case 'precision': //case 'signed': //case 'precision': //case 'signed': case 'var': self::checkVar($report_lines, $property, $annotation, $value); break; } } } return $report_lines; }
/** * @param $multiline boolean keep this value empty, it is not used because the @multiline * annotation is automatically used * @param $values string[] keep this value empty, it is not used because the @values annotation * is automatically used * @return Element */ protected function buildString($multiline = false, $values = null) { $values_captions = []; $values = $this->property->getListAnnotation('values')->values(); foreach ($values as $value) { $values_captions[$value] = Names::propertyToDisplay($value); } if ($values_captions && !in_array($this->value, $values_captions)) { $values_captions[$this->value] = $this->value; } $element = parent::buildString($this->property->getAnnotation('multiline')->value, $values_captions); if ($this->property->getAnnotation('mandatory')->value) { $element->setAttribute('required', true); } if ($this->property->getAnnotation('password')->value) { $element->setAttribute('type', 'password'); $element->setAttribute('value', strlen($this->value) ? Password::UNCHANGED : ''); } return $element; }
/** * @param $join Join * @param $master_path string * @param $master_property_name string * @param $foreign_path string * @return string the foreign class name */ private function addSimpleJoin(Join $join, $master_path, $master_property_name, $foreign_path) { $foreign_class_name = null; $master_property = $this->getProperty($master_path, $master_property_name); if ($master_property) { $foreign_type = $master_property->getType(); if ($foreign_type->isMultiple() && $foreign_type->getElementTypeAsString() == 'string') { // TODO : string[] can have multiple implementations, depending on database engine // linked strings table, mysql set.. should find a way to make this common without // knowing anything about the specific $foreign_class_name = $foreign_type->asString(); } elseif (!$foreign_type->isBasic()) { $join->mode = $master_property->getAnnotation('mandatory')->value ? Join::INNER : Join::LEFT; // force LEFT if any of the properties in the master property path is not mandatory if ($join->mode == Join::INNER && $master_path) { $root_class = new Reflection_Class($this->classes['']); $property_path = ''; foreach (explode(DOT, $master_path . DOT . $master_property_name) as $property_name) { $property_path .= ($property_path ? DOT : '') . $property_name; $property = $root_class->getProperty($property_path); if (!$property || !$property->getAnnotation('mandatory')->value) { $join->mode = Join::LEFT; break; } } } if ($foreign_type->isMultiple()) { $foreign_class_name = $foreign_type->getElementTypeAsString(); $foreign_property_name = $master_property->getAnnotation('foreign')->value; if (property_exists($foreign_class_name, $foreign_property_name) && $master_property->getAnnotation('link')->value != Link_Annotation::MAP) { $foreign_property = new Reflection_Property($foreign_class_name, $foreign_property_name); $join->foreign_column = 'id_' . $foreign_property->getAnnotation('storage')->value; $join->master_column = 'id'; } else { $this->addLinkedJoin($join, $master_path, $foreign_path, $foreign_class_name, $master_property); } } else { $foreign_class_name = $foreign_type->asString(); $join->master_column = 'id_' . $master_property->getAnnotation('storage')->value; $join->foreign_column = 'id'; } } } return $foreign_class_name; }
/** * @param $class_name string Main class name * @param $properties_path string[] $property_path = string[integer $column_number] * @return array [$property_link = string[string $property_path], $property_column = string[string $property_path]] */ private static function getPropertiesLinkAndColumn($class_name, $properties_path) { $properties_link = ['' => Link_Annotation::OBJECT]; $properties_column = []; foreach ($properties_path as $col_number => $property_path) { $property_path = str_replace('*', '', $property_path); $path = ''; foreach (explode(DOT, $property_path) as $property_name) { $path .= ($path ? DOT : '') . $property_name; try { $property = new Reflection_Property($class_name, $path); $properties_link[$path] = $property->getAnnotation('link')->value; } catch (ReflectionException $exception) { $properties_link[$path] = ''; } } $i = strrpos($property_path, DOT); $property_name = substr($property_path, $i === false ? 0 : $i + 1); $property_path = substr($property_path, 0, $i); $properties_column[$property_path][$property_name] = $col_number; } foreach (array_keys($properties_column) as $property_path) { if ($property_path) { $path = ''; foreach (explode(DOT, $property_path) as $property_name) { if (!isset($properties_column[$path][$property_name])) { $properties_column[$path][$property_name] = $path . ($path ? DOT : '') . $property_name; } $path .= ($path ? DOT : '') . $property_name; } } } return [$properties_link, $properties_column]; }
/** * @param $description string * @param $class_name string * @param $property_name string * @param $annotation_name string * @param $assumed_value mixed */ private function testAnnotation($description, $class_name, $property_name, $annotation_name, $assumed_value) { $property = new Reflection_Property($class_name, $property_name); $annotation = $property->getAnnotation($annotation_name); $this->assume($class_name . DOT . $property_name . AT . $annotation_name . SP . DQ . $assumed_value . DQ . SP . '(' . $description . ')', $annotation->value, $assumed_value); }
/** * Write a component collection property value * * Ie when you write an order, it's implicitly needed to write it's lines * * @param $object object * @param $property Reflection_Property * @param $collection Component[] */ private function writeCollection($object, Reflection_Property $property, $collection) { // old collection $class_name = get_class($object); $old_object = Search_Object::create($class_name); $this->setObjectIdentifier($old_object, $this->getObjectIdentifier($object)); $aop_getter_ignore = Getter::$ignore; Getter::$ignore = false; $old_collection = $property->getValue($old_object); Getter::$ignore = $aop_getter_ignore; $element_class = $property->getType()->asReflectionClass(); /** @var $element_link Class_\Link_Annotation */ $element_link = $element_class->getAnnotation('link'); // collection properties : write each of them $id_set = []; if ($collection) { foreach ($collection as $key => $element) { if (!is_a($element, $element_class->getName())) { $collection[$key] = $element = Builder::createClone($element, $element_class->getName(), [$element_link->getLinkClass()->getCompositeProperty()->name => $element]); } $element->setComposite($object, $property->getAnnotation('foreign')->value); $id = $element_link->value ? $this->getLinkObjectIdentifier($element, $element_link) : $this->getObjectIdentifier($element); if (!empty($id)) { $id_set[$id] = true; } $this->write($element); } } // remove old unused elements foreach ($old_collection as $old_element) { $id = $element_link->value ? $this->getLinkObjectIdentifier($old_element, $element_link) : $this->getObjectIdentifier($old_element); if (!isset($id_set[$id])) { $this->delete($old_element); } } }
/** * @param $object object * @param $property Reflection_Property * @param $value string * @param $null_if_empty boolean * @return boolean true if property value is null */ private function buildProperty($object, Reflection_Property $property, $value, $null_if_empty) { $is_null = $null_if_empty; // use widget if ($this->from_form && ($builder = $property->getAnnotation('widget')->value) && is_a($builder, Property::class, true)) { $builder = Builder::create($builder, [$property, $value]); /** @var $builder Property */ $value2 = $builder->buildValue($object, $null_if_empty); if ($value2 !== Property::DONT_BUILD_VALUE) { $value = $value2; $done = true; } } if (!isset($done)) { $type = $property->getType(); if ($type->isBasic()) { // password if ($encryption = $property->getAnnotation('password')->value) { if ($value == Password::UNCHANGED) { return true; } $value = (new Password($value, $encryption))->encrypted(); } else { $value = $this->buildBasicValue($property, $value); } } elseif (is_array($value)) { $link = $property->getAnnotation('link')->value; // object if ($link == Link_Annotation::OBJECT) { $class_name = $property->getType()->asString(); $value = $this->buildObjectValue($class_name, $value, $null_if_empty); } elseif ($link == Link_Annotation::COLLECTION) { $class_name = $property->getType()->getElementTypeAsString(); $value = $this->buildCollection($class_name, $value, $null_if_empty, $object); } else { $value = $this->buildMap($value, $property->getType()->getElementTypeAsString(), $link); } } elseif (isset($value) && $property->getAnnotation('output')->value == 'string') { /** @var $object_value Stringable */ $object_value = Builder::create($property->getType()->asString()); $object_value->fromString($value); $value = $object_value; } } // the property value is set only for official properties, if not default and not empty $property_name = $property->name; if ($value !== '' || !$property->getType()->isClass()) { $object->{$property_name} = $value; } if (!$property->isValueEmptyOrDefault($value)) { $is_null = false; } return $is_null; }
/** * Builds column name from a property * * If property data type is an object, the property name will be prefixed with 'id_'. * Array properties will return null as no column should be associated to them. * * @param $property Reflection_Property * @return string|null */ public static function buildColumnName(Reflection_Property $property) { $type = $property->getType(); return $type->isBasic() ? $property->getAnnotation('storage')->value : ($type->isMultiple() ? null : 'id_' . $property->getAnnotation('storage')->value); }
/** * Gets mysql expression for a property type * * @param $property Reflection_Property * @return string */ private static function propertyTypeToMysql(Reflection_Property $property) { $property_type = $property->getType(); if ($property_type->isBasic() || $property->getAnnotation('store')->value == 'string') { if ($property_type->hasSize()) { /** @var integer $max_length */ $max_length = $property->getAnnotation('max_length')->value; $max_value = $property->getAnnotation('max_value')->value; $min_value = $property->getAnnotation('min_value')->value; $precision = $property->getAnnotation('precision')->value; $signed = $property->getAnnotation('signed')->value; $signed_length = $max_length + ($signed ? 1 : 0); if (!isset($max_length)) { if ($property_type->isNumeric()) { $max_length = 18; $signed_length = 18; } else { $max_length = 255; } } if ($property_type->isInteger()) { if (!isset($signed) && $max_value < 0 || $min_value < 0) { $signed = true; } return $max_length <= 3 && $min_value >= -128 && $max_value <= 127 && $signed ? 'tinyint(' . $signed_length . ')' : ($max_length <= 3 && $min_value >= 0 && $max_value <= 255 && !$signed ? 'tinyint(' . $max_length . ') unsigned' : ($max_length <= 5 && $min_value >= -32768 && $max_value <= 32767 ? 'smallint(' . $signed_length . ')' : ($max_length <= 5 && $min_value >= 0 && $max_value <= 65535 ? 'smallint(' . $max_length . ') unsigned' : ($max_length <= 7 && $min_value >= -8388608 && $max_value <= 8388607 ? 'mediumint(' . $signed_length . ')' : ($max_length <= 8 && $min_value >= 0 && $max_value <= 16777215 ? 'mediumint(' . $max_length . ') unsigned' : ($max_length <= 10 && $min_value >= -2147483648.0 && $max_value <= 2147483647 ? 'int(' . $signed_length . ')' : ($max_length <= 10 && $min_value >= 0 && $max_value <= 4294967295.0 ? 'int(' . $max_length . ') unsigned' : ($max_length <= 19 && $min_value >= -9.223372036854776E+18 && $max_value <= 9.223372036854776E+18 ? 'bigint(' . $signed_length . ')' : 'bigint(' . $max_length . ') unsigned')))))))); } elseif ($property_type->isFloat()) { return $precision ? 'decimal(' . $signed_length . ', ' . $precision . ')' : 'double'; } elseif ($property->getAnnotation('binary')->value) { return $max_length <= 65535 ? 'blob' : ($max_length <= 16777215 ? 'mediumblob' : 'longblob'); } else { return $max_length <= 3 ? 'char(' . $max_length . ')' : ($max_length <= 255 ? 'varchar(' . $max_length . ')' : ($max_length <= 65535 ? 'text' : ($max_length <= 16777215 ? 'mediumtext' : 'longtext'))) . ' CHARACTER SET utf8 COLLATE utf8_general_ci'; } } switch ($property_type->asString()) { case Type::_ARRAY: return null; case Type::BOOLEAN: return 'tinyint(1)'; case Type::_CALLABLE: return null; case Type::null: case Type::NULL: return null; case Type::RESOURCE: return null; case DateTime::class: case Date_Time::class: return 'datetime'; default: return 'char(255)'; } } elseif ($property_type->asString() === Type::STRING_ARRAY) { /** @var $values string[] */ $values = []; foreach ($property->getListAnnotation('values')->values() as $key => $value) { $values[$key] = str_replace(Q, Q . Q, $value); } return $values ? ($property->getAnnotation('set')->value ? 'set' : 'enum') . "('" . join("','", $values) . "')" : 'text'; } else { return 'bigint(18) unsigned'; } }
/** * Generic getter for an object * * @param $stored mixed actual value of the object, or identifier to an object, or null * @param $class_name string the object class name * @param $object object the parent object * @param $property string|Reflection_Property the parent property * @return object */ public static function getObject(&$stored, $class_name, $object = null, $property = null) { if (!(self::$ignore || is_object($stored))) { if ($property instanceof Reflection_Property) { $property_name = $property->name; } elseif (is_string($property) && is_object($object)) { $property_name = $property; $property = new Reflection_Property(get_class($object), $property_name); } if (is_object($object) && isset($property_name)) { $id_property_name = 'id_' . $property_name; if (isset($object->{$id_property_name})) { $stored = $object->{$id_property_name}; } } if (isset($stored)) { if (isset($property) && $property->getAnnotation('store')->value == 'string') { /** @var $stored_object Stringable */ $stored_object = Builder::create($property->getType()->asString()); $stored_object->fromString($stored); $stored = $stored_object; } else { $stored = isset($property) && ($dao = $property->getAnnotation('dao')->value) ? Dao::get($dao)->read($stored, $class_name) : Dao::read($stored, $class_name); } } } return $stored; }
/** * Test property @getter : setting annotation value */ public function testGetterAnnotationSet() { $this->method('@getter : setting annotation value'); $property = new Reflection_Property(self::class, 'property'); // @getter methodName $this->assume('methodName', (new Getter_Annotation('testGetterAnnotation', $property))->value, __CLASS__ . '::testGetterAnnotation'); // @getter Local_Class_Name::methodName $this->assume('Local_Class_Name::methodName', (new Getter_Annotation('User_Annotation::has', $property))->value, User_Annotation::class . '::has'); // @getter Distant\Class\Full\Path::methodName $this->assume('Distant\\Class\\Full\\Path\\Class_Name::methodName', (new Getter_Annotation(Annoted::class . '::has', $property))->value, Annoted::class . '::has'); // use Distant\Class\Full\Path\Class_Name // @getter Class_Name::methodName $this->assume('use Class_Name::methodName', (new Getter_Annotation('Annoted::has', $property))->value, Annoted::class . '::has'); // default value for getter when there is a @link annotation $this->assume('default value when @link', $property->getAnnotation('getter')->value, Getter::class . '::getCollection'); }
/** * @param $property Reflection_Property * @return string */ private static function propertyOnDeleteToMysql(Reflection_Property $property) { return $property->getAnnotation('composite')->value ? 'CASCADE' : 'RESTRICT'; }