php-yaoi/php-yaoi

View on GitHub
src/Database/Entity.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

namespace Yaoi\Database;

use Yaoi\BaseClass;
use Yaoi\Database;
use Yaoi\Database\Definition\Column;
use Yaoi\Database\Definition\Index;
use Yaoi\Database\Definition\Table;
use Yaoi\Mappable;
use Yaoi\Sql\SelectInterface;
use Yaoi\Sql\SimpleExpression;
use Yaoi\Sql\Symbol;
use Yaoi\String\Utils;
use Yaoi\Undefined;

abstract class Entity extends BaseClass implements Mappable\Contract, Entity\Contract
{
    /**
     * @var Table[]
     */
    private static $tables = array();

    private static $settingColumns = array();

    /**
     * Method should return table definition of entity
     * @return Table
     */
    public static function table($alias = null)
    {
        $className = get_called_class();
        $tableKey = $className . ($alias ? ':' . $alias : '');
        $table = &self::$tables[$tableKey];
        if (null !== $table) {
            return $table;
        }
        /*
        if (isset(self::$settingColumns[$tableKey])) {
            throw new Exception('Already setting columns');
        }
        $columns = new \stdClass();
        self::$settingColumns[$tableKey] = $columns;
        unset(self::$settingColumns[$tableKey]);
        */
        $schemaName = Utils::fromCamelCase(str_replace('\\', '', $className));
        $table = new Table(null, self::getDatabase($className), $schemaName);
        $table->entityClassName = $className;
        $table->alias = $alias;
        static::setUpColumns($table->columns);
        static::setUpTable($table, $table->columns);

        return $table;
    }

    /**
     * @param Table|null $table
     * @return static
     */
    public static function columns(Table $table = null)
    {
        return null === $table
            ? static::table()->columns
            : $table->columns;
    }


    protected $persistent;


    /**
     * @param null|static $filter
     * @return SelectInterface
     */
    public static function statement($filter = null)
    {
        $className = get_called_class();
        $table = static::table();
        $statement = $table->database()->select($table);
        $statement->bindResultClass($className);

        if ($filter instanceof static) {
            foreach ($filter->toArray(true) as $name => $value) {
                $statement->where("? = ?", new Symbol($table->schemaName, $name), $value);
            }
        }
        return $statement;
    }

    /**
     * @param ...$id
     * @return static
     * @throws \Yaoi\Entity\Exception
     * @todo testdoc
     */
    public static function findByPrimaryKey($id)
    {
        $className = get_called_class();
        $table = static::table();
        $statement = $table->database()->select($table);
        $statement->bindResultClass($className);

        $args = func_get_args();
        $i = 0;
        foreach ($table->primaryKey as $keyField) {
            if (!isset($args[$i])) {
                throw new \Yaoi\Entity\Exception('Full primary key required', \Yaoi\Entity\Exception::KEY_MISSING);
            }
            $keyValue = $args[$i++];
            $statement->where('? = ?', new Symbol($table->schemaName, $keyField->schemaName), $keyValue);
        }
        return $statement->query()->fetchRow();
    }

    /**
     * @return static
     */
    public function findSaved()
    {
        $table = $this->table();
        $statement = self::statement();
        $data = $this->toArray(true);

        $uniqueIndex = false;


        foreach ($table->indexes as $index) {
            if ($index->type !== Index::TYPE_UNIQUE && $index->type !== Index::TYPE_PRIMARY) {
                continue;
            }

            $uniqueIndex = new SimpleExpression();

            foreach ($index->columns as $column) {
                if ($column->flags & Column::AUTO_ID) {
                    $uniqueIndex = false;
                    break;
                }

                $schemaName = $column->schemaName;
                // skip unique index if null column found
                if (!isset($data[$schemaName])) {
                    $uniqueIndex = false;
                    break;
                }
                $uniqueIndex->andExpr(
                    '? = ?',
                    new Symbol($schemaName),
                    $data[$schemaName]
                );
            }

            if ($uniqueIndex) {
                break;
            }
        }

        if ($uniqueIndex) {
            $statement->where($uniqueIndex);
        } else {
            foreach ($table->getColumns(true) as $column) {
                $schemaName = $column->schemaName;

                if (!isset($data[$schemaName])) {
                    continue;
                }

                $statement->where(
                    '? = ?',
                    new Symbol($schemaName),
                    $data[$schemaName]
                );
            }
        }


        /** @var static $item */
        $item = $statement->query()->fetchRow();
        return $item;
    }


    private $originalData;

    static function fromArray(array $row, $object = null, $source = null)
    {
        if (is_null($object)) {
            $object = new static;
        }

        $object->originalData = array();
        foreach (static::table()->getColumns(true) as $column) {
            if (array_key_exists($column->schemaName, $row)) {
                $value = Column::castField($row[$column->schemaName], $column->flags, true);
                $object->{$column->propertyName} = $value;
                $object->originalData [$column->schemaName] = $value;
            }
        }

        if ($source) {
            $object->persistent = true;
        }

        return $object;
    }

    public function __construct()
    {
        foreach (static::table()->getColumns(true) as $column) {
            if (null === $this->{$column->propertyName}) {
                $this->{$column->propertyName} = Undefined::get();
            }
        }
    }


    public function toArray($skipNotSetProperties = false, $skipCast = false)
    {
        $result = array();
        foreach (static::table()->getColumns(true) as $column) {
            $value = $this->{$column->propertyName};
            if ($value instanceof Undefined) {
                if ($skipNotSetProperties) {
                    continue;
                } else {
                    $value = $column->getDefault();
                }
            } elseif (!$skipCast) {
                $value = Column::castField($value, $column->flags, false);
            }

            $result[$column->schemaName] = $value;
        }
        return $result;
    }

    public function save()
    {
        if ($this->persistent) {
            $this->update();
        } else {
            $this->insert();
        }
    }


    public function delete()
    {
        $table = static::table();
        $delete = $table->database()->delete($table->schemaName);
        $data = $this->toArray();

        foreach ($table->primaryKey as $keyField) {
            if (!isset($this->{$keyField->schemaName})) {
                throw new \Yaoi\Entity\Exception('Primary key required for delete', \Yaoi\Entity\Exception::KEY_MISSING);
            }
            $delete->where("? = ?", new Symbol($keyField->schemaName), $data[$keyField->schemaName]);
        }

        $delete->query();
        $this->persistent = false;
        return $this;
    }


    public function update()
    {
        $table = static::table();
        $update = $table->database()->update($table->schemaName);
        $data = $this->toArray(true);
        $newData = $data;

        foreach ($table->primaryKey as $keyField) {
            if (!isset($this->originalData[$keyField->schemaName])) {
                throw new \Yaoi\Entity\Exception('Primary key required for update', \Yaoi\Entity\Exception::KEY_MISSING);
            }
            $update->where("? = ?", new Symbol($keyField->schemaName), $this->originalData[$keyField->schemaName]);
        }

        foreach ($data as $key => $value) {
            if ($this->originalData[$key] === $value) {
                unset($data[$key]);
            }
        }
        $this->persistent = true;
        if (empty($data)) {
            return $this;
        }

        $update->set($data);
        $update->query();
        $this->originalData = $newData;
        return $this;
    }

    public function insert()
    {
        $table = static::table();
        $insert = $table->database()->insert($table->schemaName);
        $data = $this->toArray(true);

        $autoId = $table->autoIdColumn;

        $insert->valuesRow($data);

        $query = $insert->query();
        $this->originalData = $data;

        if ($autoId) {
            if ($this->{$autoId->propertyName} instanceof Undefined) {
                $lastId = $query->lastInsertId();
                $this->{$autoId->propertyName} = $lastId;
                $this->originalData[$autoId->schemaName] = $lastId;
            }
        }

        $this->persistent = true;
        return $this;
    }

    /**
     * @var Contract[]
     */
    private static $databases = array();

    public static function bindDatabase(Contract $database, $forceTableCacheClean = false)
    {
        $class = get_called_class();
        if ($forceTableCacheClean || !isset(self::$databases[$class]) || self::$databases[$class] !== $database) {
            self::$databases[$class] = $database;
            if (isset(self::$tables[$class])) {
                unset(self::$tables[$class]);
            }
        }
    }

    private static function getDatabase($class)
    {
        if (isset(self::$databases[$class])) {
            return self::$databases[$class];
        } else {
            return Database::getInstance();
        }
    }


    public function findOrSave($updateRecord = false)
    {
        $item = $this->findSaved();

        if (!$item) {
            $this->save();
        } elseif (!$updateRecord) {
            self::fromArray($item->toArray(), $this);
            $this->persistent = true;
        }

        return $this;
    }

    /**
     * @return Entity\Migration
     */
    public static function migration()
    {
        return static::table()->migration();
    }


}