Skip to content
Snippets Groups Projects
SimpleORMap.class.php 83.4 KiB
Newer Older
                throw new UnexpectedValueException(sprintf('Column %s not found for alias %s', $field, $alias));
            }
        }
        foreach (array_keys($this->relations) as $one) {
            $this->relations[$one] = null;
        }
        $this->additional_data = [];
    }

    /**
     * checks if at least one field was modified since last restore
     *
     * @return boolean
     */
    public function isDirty()
    {
        foreach (array_keys($this->db_fields) as $field) {
            if ($this->isFieldDirty($field)) {
                return true;
            }
        }
        return false;
    }

    /**
     * checks if given field was modified since last restore
     *
     * @param string $field
     * @return boolean
     */
    public function isFieldDirty($field)
    {
        if ($this->content[$field] === null || $this->content_db[$field] === null) {
            return $this->content[$field] !== $this->content_db[$field];
        } else if ($this->content[$field] instanceof I18NString || $this->content_db[$field] instanceof I18NString) {
            return $this->content[$field] != $this->content_db[$field];
        } else {
            return (string)$this->content[$field] !== (string)$this->content_db[$field];
        }
    }

    /**
     * reverts value of given field to last restored value
     *
     * @param string $field
     * @return mixed the restored value
     */
    public function revertValue($field)
    {
        return ($this->content[$field] = $this->content_db[$field]);
    }

    /**
     * returns unmodified value of given field
     *
     * @param string $field
     * @throws InvalidArgumentException
     * @return mixed
     */
    public function getPristineValue($field)
    {
        if (array_key_exists($field, $this->content_db)) {
            return $this->content_db[$field];
        } else {
            throw new InvalidArgumentException(get_class($this) . '::'. $field . ' not found.');
        }
    }

    /**
     * intitalize a relationship and get related record(s)
     *
     * @param string $relation name of relation
     * @throws InvalidArgumentException if the relation does not exists
     * @return void
     */
    public function initRelation($relation)
    {
        if (!array_key_exists($relation, $this->relations)) {
            throw new InvalidArgumentException('Unknown relation: ' . $relation);
        }
        if ($this->relations[$relation] === null) {
            $options = $this->getRelationOptions($relation);
            $to_call = [$options['class_name'], $options['assoc_func']];
            if (!is_callable($to_call)) {
                throw new RuntimeException('assoc_func: ' . join('::', $to_call) . ' is not callable.' );
            }
            $params = $options['assoc_func_params_func'];
            if ($options['type'] === 'has_many') {
                $records = function($record) use ($to_call, $params, $options) {$p = (array)$params($record); return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options['order_by']]));};
                $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this);
            } elseif ($options['type'] === 'has_and_belongs_to_many') {
                $records = function($record) use ($to_call, $params, $options) {$p = (array)$params($record); return call_user_func_array($to_call, array_merge(count($p) ? $p : [null], [$options]));};
                $this->relations[$relation] = new SimpleORMapCollection($records, $options, $this);
            } else {
                $p = (array)$params($this);
                $records = call_user_func_array($to_call, count($p) ? $p : [null]);
                $result = is_array($records) ? $records[0] : $records;
                $this->relations[$relation] = $result;
            }
        }
    }

    /**
     * clear data for a relationship
     *
     * @param string $relation name of relation
     * @throws InvalidArgumentException if teh relation does not exists
     */
    public function resetRelation($relation)
    {
        if (!array_key_exists($relation, $this->relations)) {
            throw new InvalidArgumentException('Unknown relation: ' . $relation);
        }
        $this->relations[$relation] = null;
    }

    /**
     * invoke registered callbacks for given type
     * if one callback returns false the following will not
     * be invoked
     *
     * @param string $type type of callback
     * @return bool return value from last callback
     */
    protected function applyCallbacks($type)
    {
        $ok = true;
        foreach ($this->registered_callbacks[$type] as $cb) {
            if ($cb instanceof Closure) {
                $function =  $cb;
                $params = [$this, $type, $cb];
            } else {
                $function = [$this, $cb];
                $params = [$type];
            };
            $ok = call_user_func_array($function, $params);
            if ($ok === false) {
                break;
            }
        }
        return $ok;
    }

    /**
     * register given callback for one or many possible callback types
     * callback param could be a closure or method name of current class
     *
     * @param string|array $types types to register callback for
     * @param mixed $cb callback
     * @throws InvalidArgumentException if the callback type is not known
     * @return number of registered callbacks
     */
    protected function registerCallback($types, $cb)
    {
        $types = is_array($types) ? $types : words($types);
        foreach ($types as $type) {
            if (isset($this->registered_callbacks[$type])) {
                $this->registered_callbacks[$type][] = $cb;
                $reg++;
            } else {
                throw new InvalidArgumentException('Unknown callback type: ' . $type);
            }
        }
        return $reg;
    }

    /**
     * unregister given callback for one or many possible callback types
     *
     * @param string|array $types types to unregister callback for
     * @param mixed $cb
     * @throws InvalidArgumentException if the callback type is not known
     * @return number of unregistered callbacks
     */
    protected function unregisterCallback($types, $cb)
    {
        $types = is_array($types) ? $types : words($types);
        foreach ($types as $type) {
            if (isset($this->registered_callbacks[$type])) {
                $found = array_search($cb, $this->registered_callbacks[$type], true);
                if ($found !== false) {
                    $unreg++;
                    unset($this->registered_callbacks[$type][$found]);
                }
            } else {
                throw new InvalidArgumentException('Unknown callback type: ' . $type);
            }
        }
        return $unreg;
    }

    /**
     * default callback for tables with auto_increment primary key
     *
     * @param string $type callback type
     * @return boolean
     */
    protected function cbAutoIncrementColumn($type)
    {
        if ($type == 'after_create' && !$this->getId()) {
            $this->setId(DBManager::get()->lastInsertId());
        }
        if ($type == 'before_store' && $this->isNew() && $this->getId() === null) {
            $this->setId(0);
        }
        return true;
    }

    /**
     * default callback for tables without auto_increment
     */
    protected function cbAutoKeyCreation()
    {
        if ($this->isNew() && $this->getId() === null) {
            $this->setId($this->getNewId());
        }
    }

    /**
     * default callback used to map specific callbacks to NotificationCenter
     *
     * @param string $cb_type callback type
     * @return boolean
     */
    protected function cbNotificationMapper($cb_type)
    {
        if (isset($this->notification_map[$cb_type])) {
            try {
                foreach(words($this->notification_map[$cb_type]) as $notification) {
                    NotificationCenter::postNotification($notification, $this);
                }
            } catch (NotificationVetoException $e) {
                return false;
            }
        }
    }

    /**
     * default callback used to map specific callbacks to NotificationCenter
     *
     * @param string $cb_type callback type
     * @return boolean
     */
    protected function cbAfterInitialize($cb_type)
    {
        foreach (array_keys($this->db_fields) as $field) {
            if (is_object($this->content[$field])) {
                $this->content_db[$field] = clone $this->content[$field];
            } else {
                $this->content_db[$field] = $this->content[$field];
            }
        }
    }

    /**
     * default setter used to proxy serialized fields with
     * ArrayObjects
     *
     * @param string $field column name
     * @param mixed $value value
     * @return mixed
     */
     protected function setSerializedValue($field, $value)
     {
         $object_type = $this->serialized_fields[$field];
         if (is_null($value) || $value instanceof $object_type) {
             $this->content[$field] = $value;
         } else {
             $this->content[$field] = new $object_type($value);
         }
         return $this->content[$field];
     }

    /**
     * default setter used to proxy I18N fields with
     * I18NString
     *
     * @param string $field column name
     * @param mixed $value value
     * @return mixed
     */
    protected function setI18nValue($field, $value)
    {
        $meta = ['object_id' => $this->getId(),
                 'table'     => $this->db_table,
                 'field'     => $field];
        if ($value instanceof I18NString) {
            $value->setMetadata($meta);
            $this->content[$field] = $value;
        } else {
            $this->content[$field] = new I18NString($value, null, $meta);
        }
        return $this->content[$field];
    }

    /**
     * default callback for tables with I18N fields
     * @param $type
     * @return bool
     */
    protected function cbI18N($type)
    {

        if ($type == 'before_store') {
            $i18ncontent = [];
            foreach (array_keys($this->i18n_fields) as $field) {
                if ($this->content[$field] instanceof I18NString) {
                    $i18ncontent[$field] = $this->content[$field];
                    $this->content[$field] = $this->content[$field]->original();
                    $this->content_db[$field] = $this->content_db[$field]->original();
                }
            }
            if (count($i18ncontent)) {
                $after_store = function($that, $type, $myself) use ($i18ncontent) {
                    foreach ($i18ncontent as $field => $one) {
                        $meta = ['object_id' => $this->getId(),
                                 'table'     => $this->db_table,
                                 'field'     => $field];
                        $one->setMetadata($meta);
                        $one->storeTranslations();
                        if (!$this->content[$field] instanceof I18NString) {
                            $this->content[$field] = $one;
                            $this->content_db[$field] = clone $one;
                        }
                    }
                    $this->unregisterCallback('after_store', $myself);
                };
                $this->registerCallback('after_store', $after_store);
            }

        }

        if ($type == 'after_delete') {
            foreach (array_keys($this->i18n_fields) as $field) {
                if ($this->content[$field] instanceof I18NString) {
                    $this->content[$field]->removeTranslations();
                }
            }
        }
        return true;
    }

     /**
      * Cleans up this object. This essentially reset all relations of
      * this object and marks them as unused so that the garbage collector may
      * remove the objects.
      *
      * Use this function when you ran into memory problems and need to free
      * some memory;
      */
     public function cleanup()
     {
         foreach ($this->relations as $relation => $object) {
             if ($object instanceof SimpleORMap || $object instanceof SimpleORMapCollection) {
                 $this->relations[$relation]->cleanup();
             }
             $this->resetRelation($relation);
         }
     }
}