/** * Build columns list for an object, in order to instantiate this object when read * * @param $path string * @param $join Join * @param $first_property boolean * @return string */ private function buildObjectColumns($path, Join $join, &$first_property) { $sql_columns = ''; if ($this->expand_objects) { foreach ($this->joins->getProperties($path) as $property) { $column_name = Sql\Builder::buildColumnName($property); if ($column_name) { if ($first_property) { $first_property = false; } else { $sql_columns .= ', '; } $sql_columns .= $join->foreign_alias . DOT . BQ . $column_name . BQ . ($this->append || !$this->resolve_aliases ? '' : ' AS ' . BQ . $path . ':' . $property->name . BQ); } } if ($first_property) { $first_property = false; } else { $sql_columns .= ', '; } $sql_columns .= $join->foreign_alias . '.id' . ($this->append || !$this->resolve_aliases ? '' : ' AS ' . BQ . $path . ':id' . BQ); } else { if ($first_property) { $first_property = false; } else { $sql_columns .= ', '; } $sql_columns .= $join->foreign_alias . '.id' . ($this->resolve_aliases ? ' AS ' . BQ . $path . BQ : ''); } return $sql_columns; }
/** * Write an object into data source * * If object was originally read from data source, corresponding data will be overwritten * If object was not originally read from data source nor linked to it using replace(), a new * record will be written into data source using this object's data. * If object is null (all properties null or unset), the object will be removed from data source * * TODO LOWEST factorize this to become SOLID * * @param $object object object to write into data source * @param $options Option[] some options for advanced write * @return object the written object if written, or null if the object could not be written */ public function write($object, $options = []) { if ($this->beforeWrite($object, $options)) { if (Null_Object::isNull($object)) { $this->disconnect($object); } $class = new Link_Class(get_class($object)); $id_property = 'id'; foreach ($options as $option) { if ($option instanceof Only) { $only = isset($only) ? array_merge($only, $option->properties) : $option->properties; } } do { /** @var $link Class_\Link_Annotation */ $link = $class->getAnnotation('link'); if ($link->value) { $link_property = $link->getLinkClass()->getLinkProperty(); $link_object = $link_property->getValue($object); if (!$link_object) { $id_link_property = 'id_' . $link_property->name; $object->{$id_link_property} = $this->write($link_object, $options); } } $table_columns_names = array_keys($this->getStoredProperties($class)); $write_collections = []; $write_maps = []; $write = []; $aop_getter_ignore = Getter::$ignore; Getter::$ignore = true; $exclude_properties = $link->value ? array_keys((new Reflection_Class($link->value))->getProperties([T_EXTENDS, T_USE])) : []; /** @var $properties Reflection_Property[] */ $properties = $class->accessProperties(); $properties = Replaces_Annotations::removeReplacedProperties($properties); foreach ($properties as $property) { if (!isset($only) || in_array($property->name, $only)) { if (!$property->isStatic() && !in_array($property->name, $exclude_properties)) { $value = isset($object->{$property}) ? $property->getValue($object) : null; $property_is_null = $property->getAnnotation('null')->value; if (is_null($value) && !$property_is_null) { $value = ''; } if (in_array($property->name, $table_columns_names)) { $element_type = $property->getType()->getElementType(); // write basic if ($element_type->isBasic()) { $write[$property->getAnnotation('storage')->value] = is_array($value) ? json_encode($value) : $value; } elseif ($property->getAnnotation('store')->value == 'string') { $write[$property->getAnnotation('storage')->value] = is_array($value) ? serialize($value) : strval($value); } else { $column_name = 'id_' . $property->name; if (is_object($value)) { $value_class = new Link_Class(get_class($value)); $id_value = $value_class->getLinkedClassName() && !$element_type->asReflectionClass()->getAnnotation('link')->value ? 'id_' . $value_class->getCompositeProperty()->name : 'id'; $object->{$column_name} = $this->getObjectIdentifier($value, $id_value); if (empty($object->{$column_name})) { $object->{$column_name} = $this->getObjectIdentifier($this->write($value), $id_value); } } $write['id_' . $property->getAnnotation('storage')->value] = $property_is_null && !isset($object->{$column_name}) ? null : intval($object->{$column_name}); } } elseif (is_array($value) && $property->getAnnotation('link')->value == Link_Annotation::COLLECTION) { $write_collections[] = [$property, $value]; } elseif (is_array($value) && $property->getAnnotation('link')->value == Link_Annotation::MAP) { foreach ($value as $key => $val) { if (!is_object($val)) { $val = Dao::read($val, $property->getType()->getElementTypeAsString()); if (isset($val)) { $value[$key] = $val; } else { unset($value[$key]); } } } $write_maps[] = [$property, $value]; } } } } Getter::$ignore = $aop_getter_ignore; if ($write) { // link class : id is the couple of composite properties values if ($link->value) { $search = []; foreach ($link->getLinkProperties() as $property) { $property_name = $property->getName(); $column_name = $property->getType()->isClass() ? 'id_' : ''; $column_name .= $properties[$property_name]->getAnnotation('storage')->value; if (isset($write[$column_name])) { $search[$property_name] = $write[$column_name]; } elseif (isset($write[$property_name])) { $search[$property_name] = $write[$column_name]; } else { trigger_error("Can't search {$property_name}", E_USER_ERROR); } } if ($this->search($search, $class->name)) { $id = []; foreach ($search as $property_name => $value) { $column_name = $properties[$property_name]->getAnnotation('storage')->value; if (isset($write['id_' . $column_name])) { $column_name = 'id_' . $column_name; } $id[$column_name] = $value; unset($write[$column_name]); } } else { $id = null; } } else { $id = $this->getObjectIdentifier($object, $id_property); } if ($write) { $this->setContext($class->name); if (empty($id)) { $this->disconnect($object); $id = $this->query(Sql\Builder::buildInsert($class->name, $write)); if (!empty($id)) { $this->setObjectIdentifier($object, $id); } } else { $this->query(Sql\Builder::buildUpdate($class->name, $write, $id)); } } } foreach ($write_collections as $write) { list($property, $value) = $write; $this->writeCollection($object, $property, $value); } foreach ($write_maps as $write) { list($property, $value) = $write; $this->writeMap($object, $property, $value); } // if link class : write linked object too $id_property = $link->value ? 'id_' . $class->getCompositeProperty()->name : null; $class = $link->value ? new Link_Class($link->value) : null; } while ($class && !Null_Object::isNull($object, $class->name)); /** @var $after_writes Method_Annotation[] */ $after_writes = (new Reflection_Class(get_class($object)))->getAnnotations('after_write'); foreach ($after_writes as $after_write) { if ($after_write->call($object, [$this, $options]) === false) { break; } } return $object; } return null; }
/** * Adds a property path to the joins list * * @param $path string full path to desired property, starting from starting class * @param $depth integer for internal use : please do not use this * @return Join the added join, or null if $path does not generate any join */ public function add($path, $depth = 0) { if (isset($this->joins[$path]) || array_key_exists($path, $this->joins)) { return $this->joins[$path]; } list($master_path, $master_property_name) = Sql\Builder::splitPropertyPath($path); if ($master_path && !isset($this->joins[$master_path])) { $this->add($master_path, $depth + 1); } if (isset($this->link_joins[$path])) { $property_type = $this->getProperties($master_path)[$master_property_name]->getType(); if ($property_type->isClass()) { $linked_master_alias = $this->link_joins[$path]->foreign_alias; } else { return $this->link_joins[$path]; } } $join = new Join(); $foreign_class_name = strpos($master_property_name, '->') ? $this->addReverseJoin($join, $master_path, $master_property_name, $path) : $this->addSimpleJoin($join, $master_path, $master_property_name, $path); $this->joins[$path] = $join->mode ? $this->addFinalize($join, $master_path, $foreign_class_name, $path, $depth) : null; if (isset($linked_master_alias)) { $join->master_alias = $linked_master_alias; } return $this->joins[$path]; }
/** * Build SQL WHERE section for given path and value * * @param $path string|integer Property path starting by a root class property (may be a numeric key, or a structure keyword) * @param $value mixed May be a value, or a structured array of multiple where clauses * @param $clause string For multiple where clauses, tell if they are linked with 'OR' or 'AND' * @return string */ private function buildPath($path, $value, $clause) { if ($value instanceof Func\Where) { $this->joins->add($path); list($master_path, $foreign_column) = Builder::splitPropertyPath($path); if ($foreign_column == 'id') { $prefix = ''; } else { $properties = $this->joins->getProperties($master_path); $property = isset($properties[$foreign_column]) ? $properties[$foreign_column] : null; $id_links = [Link_Annotation::OBJECT, Link_Annotation::COLLECTION, Link_Annotation::MAP]; $prefix = $property ? in_array($property->getAnnotation('link')->value, $id_links) ? 'id_' : '' : ''; } return $value->toSql($this, $path, $prefix); } elseif ($value instanceof Date_Time) { // TODO a class annotation (@business? @string?) could help choose $value = $value->toISO(false); } switch (gettype($value)) { case 'NULL': return ''; case 'array': return $this->buildArray($path, $value, $clause); case 'object': return $this->buildObject($path, $value); default: return $this->buildValue($path, $value); } }