<commit_before><?php

/**
 * This file is part of the ORMiny library.
 * (c) <NAME> <<EMAIL>>
 *
 * For licensing information see the LICENSE file.
 */

namespace ORMiny;

use Modules\DBAL\QueryBuilder;
use Modules\DBAL\QueryBuilder\Expression;
use ORMiny\Annotations\Relation;

class Entity
{
    const STATE_NEW     = 1;
    const STATE_HANDLED = 2;

    /**
     * @var EntityMetadata
     */
    private $metadata;

    /**
     * @var EntityManager
     */
    private $manager;

    private $originalData = [];
    private $objectStates = [];
    private $objectRelations = [];

    private $relatedObjectHandles = [];
    private $readOnlyObjectHandles = [];

    public function __construct(EntityManager $manager, EntityMetadata $metadata)
    {
        $this->manager  = $manager;
        $this->metadata = $metadata;
    }

    //Metadata related methods
    public function getMetadata()
    {
        return $this->metadata;
    }

    public function isPrimaryKeySet($object)
    {
        return $this->getPrimaryKeyValue($object) !== null;
    }

    public function getPrimaryKeyValue($object)
    {
        return $this->metadata->getFieldValue($object, $this->metadata->getPrimaryKey());
    }

    public function setFieldValue($value, $field, $object)
    {
        return $this->metadata->setFieldValue($value, $field, $object);
    }

    public function getFieldValue($object, $field)
    {
        return $this->metadata->getFieldValue($object, $field);
    }

    public function getRelationValue($object, $relationName)
    {
        return $this->metadata->getRelationValue($object, $relationName);
    }

    public function setRelationValue($object, $relationName, $value)
    {
        $this->metadata->assertObjectInstance($object);

        $relation      = $this->metadata->getRelation($relationName);
        $relatedEntity = $this->manager->get($relation->target);

        $objectId = spl_object_hash($object);

        $this->relatedObjectHandles[$objectId] = $object;

        switch ($relation->type) {
            case Relation::MANY_MANY:
                $targetKey = $relation->targetKey;

                $this->objectRelations[$objectId][$relationName] = [];
                foreach ($value as $relatedObject) {
                    $this->objectRelations[$objectId][$relationName][] = $relatedEntity->metadata->getFieldValue(
                        $relatedObject,
                        $targetKey
                    );
                }
                break;

            case Relation::HAS_MANY:
                $this->objectRelations[$objectId][$relationName] = array_map(
                    [$relatedEntity, 'getPrimaryKeyValue'],
                    (array)$value
                );
                break;

            default:
                $this->objectRelations[$objectId][$relationName] = $relatedEntity->getPrimaryKeyValue(
                    $value
                );
                break;
        }

        return $this->metadata->setRelationValue($object, $relationName, $value);
    }

    //Entity API
    public function toArray($object)
    {
        $this->metadata->assertObjectInstance($object);

        $data = [];
        foreach ($this->metadata->getFields() as $field) {
            $data[$field] = $this->metadata->getFieldValue($object, $field);
        }

        return $data;
    }

    /**
     * @return array
     */
    private function createEmptyRelationsArray()
    {
        $array = [];
        foreach ($this->metadata->getRelations() as $relationName => $relation) {
            switch ($relation->type) {
                case Relation::HAS_ONE:
                case Relation::BELONGS_TO:
                    $array[$relationName] = null;
                    break;

                default:
                    $array[$relationName] = [];
                    break;
            }
        }

        return $array;
    }

    public function create(array $data = [])
    {
        $className = $this->metadata->getClassName();
        $object    = new $className;
        array_walk($data, [$this->metadata, 'setFieldValue'], $object);

        $objectId = spl_object_hash($object);

        if ($this->isPrimaryKeySet($object)) {
            $this->objectStates[$objectId] = self::STATE_HANDLED;
        } else {
            $this->objectStates[$objectId] = self::STATE_NEW;
        }

        $this->originalData[$objectId]         = $data;
        $this->objectRelations[$objectId]      = $this->createEmptyRelationsArray($objectId);
        $this->relatedObjectHandles[$objectId] = $object;

        return $object;
    }

    public function find()
    {
        return new EntityFinder($this->manager, $this->manager->getDriver(), $this->metadata);
    }

    /**
     * @return Expression
     */
    public function expression()
    {
        return $this->manager->getDriver()->getQueryBuilder()->expression();
    }

    public function get($primaryKey)
    {
        return $this->find()->getByPrimaryKey(func_get_args());
    }

    public function delete($object)
    {
        $queryBuilder = $this->manager->getDriver()->getQueryBuilder();

        foreach ($this->metadata->getRelations() as $relation) {
            $foreignKey    = $this->metadata->getFieldValue($object, $relation->foreignKey);
            $relatedEntity = $this->manager->get($relation->target);
            switch ($relation->type) {
                case Relation::HAS_ONE:
                case Relation::HAS_MANY:
                    $relatedEntity->find()->deleteByField($relation->targetKey, $foreignKey);
                    break;

                case Relation::MANY_MANY:
                    $queryBuilder
                        ->delete($relation->joinTable)
                        ->where(
                            $queryBuilder->expression()->eq(
                                $this->metadata->getTable() . '_' . $relation->foreignKey,
                                $queryBuilder->createPositionalParameter($foreignKey)
                            )
                        )->query();
                    break;

                case Relation::BELONGS_TO:
                    //don't delete the record this one belongs to
                    break;
            }
        }

        if ($this->isPrimaryKeySet($object)) {
            $queryBuilder->delete($this->metadata->getTable())
                ->where(
                    $queryBuilder->expression()->eq(
                        $this->metadata->getPrimaryKey(),
                        $queryBuilder->createPositionalParameter(
                            $this->getPrimaryKeyValue($object)
                        )
                    )
                )->query();
            unset($this->relatedObjectHandles[spl_object_hash($object)]);
        }
    }

    public function save($object)
    {
        $this->metadata->assertObjectInstance($object);
        $objectId = spl_object_hash($object);

        if (!isset($this->objectStates[$objectId]) || $this->relatedObjectHandles[$objectId] !== $object) {
            $this->objectStates[$objectId]         = self::STATE_NEW;
            $this->originalData[$objectId]         = [];
            $this->objectRelations[$objectId]      = $this->createEmptyRelationsArray();
            $this->relatedObjectHandles[$objectId] = $object;
        } elseif (isset($this->readOnlyObjectHandles[$objectId])) {
            return;
        }

        $relationsIterator = new \ArrayIterator($this->metadata->getRelations());

        $hasOneRelations   = new \CallbackFilterIterator(
            $relationsIterator,
            function ($relation) {
                return $relation->type === Relation::HAS_ONE || $relation->type === Relation::BELONGS_TO;
            });
        $hasManyRelations  = new \CallbackFilterIterator(
            $relationsIterator,
            function ($relation) {
                return $relation->type === Relation::HAS_MANY;
            });
        $manyManyRelations = new \CallbackFilterIterator(
            $relationsIterator,
            function ($relation) {
                return $relation->type === Relation::MANY_MANY;
            });

        $modifiedManyManyRelations = [];
        foreach ($manyManyRelations as $relationName => $relation) {
            $relatedEntity  = $this->manager->get($relation->target);
            $relatedObjects = $this->metadata->getRelationValue($object, $relationName);

            $currentForeignKeys = array_map(
                function ($object) use ($relatedEntity) {
                    if (!is_object($object)) {
                        //the foreign key is set directly
                        return $object;
                    }
                    $relatedEntity->save($object);

                    return $relatedEntity->getPrimaryKeyValue($object);
                },
                $relatedObjects
            );

            $originalForeignKeys = $this->objectRelations[$objectId][$relationName];

            $modifiedManyManyRelations[$relationName] = [
                'deleted'  => array_diff($originalForeignKeys, $currentForeignKeys),
                'inserted' => array_diff($currentForeignKeys, $originalForeignKeys)
            ];<commit_msg>Add typehints.<commit_after><?php

/**
 * This file is part of the ORMiny library.
 * (c) <NAME> <<EMAIL>>
 *
 * For licensing information see the LICENSE file.
 */

namespace ORMiny;

use Modules\DBAL\QueryBuilder;
use Modules\DBAL\QueryBuilder\Expression;
use ORMiny\Annotations\Relation;

class Entity
{
    const STATE_NEW     = 1;
    const STATE_HANDLED = 2;

    /**
     * @var EntityMetadata
     */
    private $metadata;

    /**
     * @var EntityManager
     */
    private $manager;

    private $originalData = [];
    private $objectStates = [];
    private $objectRelations = [];

    private $relatedObjectHandles = [];
    private $readOnlyObjectHandles = [];

    public function __construct(EntityManager $manager, EntityMetadata $metadata)
    {
        $this->manager  = $manager;
        $this->metadata = $metadata;
    }

    //Metadata related methods
    public function getMetadata()
    {
        return $this->metadata;
    }

    public function isPrimaryKeySet($object)
    {
        return $this->getPrimaryKeyValue($object) !== null;
    }

    public function getPrimaryKeyValue($object)
    {
        return $this->metadata->getFieldValue($object, $this->metadata->getPrimaryKey());
    }

    public function setFieldValue($value, $field, $object)
    {
        return $this->metadata->setFieldValue($value, $field, $object);
    }

    public function getFieldValue($object, $field)
    {
        return $this->metadata->getFieldValue($object, $field);
    }

    public function getRelationValue($object, $relationName)
    {
        return $this->metadata->getRelationValue($object, $relationName);
    }

    public function setRelationValue($object, $relationName, $value)
    {
        $this->metadata->assertObjectInstance($object);

        $relation      = $this->metadata->getRelation($relationName);
        $relatedEntity = $this->manager->get($relation->target);

        $objectId = spl_object_hash($object);

        $this->relatedObjectHandles[$objectId] = $object;

        switch ($relation->type) {
            case Relation::MANY_MANY:
                $targetKey = $relation->targetKey;

                $this->objectRelations[$objectId][$relationName] = [];
                foreach ($value as $relatedObject) {
                    $this->objectRelations[$objectId][$relationName][] = $relatedEntity->metadata->getFieldValue(
                        $relatedObject,
                        $targetKey
                    );
                }
                break;

            case Relation::HAS_MANY:
                $this->objectRelations[$objectId][$relationName] = array_map(
                    [$relatedEntity, 'getPrimaryKeyValue'],
                    (array)$value
                );
                break;

            default:
                $this->objectRelations[$objectId][$relationName] = $relatedEntity->getPrimaryKeyValue(
                    $value
                );
                break;
        }

        return $this->metadata->setRelationValue($object, $relationName, $value);
    }

    //Entity API
    public function toArray($object)
    {
        $this->metadata->assertObjectInstance($object);

        $data = [];
        foreach ($this->metadata->getFields() as $field) {
            $data[$field] = $this->metadata->getFieldValue($object, $field);
        }

        return $data;
    }

    /**
     * @return array
     */
    private function createEmptyRelationsArray()
    {
        $array = [];
        foreach ($this->metadata->getRelations() as $relationName => $relation) {
            switch ($relation->type) {
                case Relation::HAS_ONE:
                case Relation::BELONGS_TO:
                    $array[$relationName] = null;
                    break;

                default:
                    $array[$relationName] = [];
                    break;
            }
        }

        return $array;
    }

    public function create(array $data = [])
    {
        $className = $this->metadata->getClassName();
        $object    = new $className;
        array_walk($data, [$this->metadata, 'setFieldValue'], $object);

        $objectId = spl_object_hash($object);

        if ($this->isPrimaryKeySet($object)) {
            $this->objectStates[$objectId] = self::STATE_HANDLED;
        } else {
            $this->objectStates[$objectId] = self::STATE_NEW;
        }

        $this->originalData[$objectId]         = $data;
        $this->objectRelations[$objectId]      = $this->createEmptyRelationsArray($objectId);
        $this->relatedObjectHandles[$objectId] = $object;

        return $object;
    }

    public function find()
    {
        return new EntityFinder($this->manager, $this->manager->getDriver(), $this->metadata);
    }

    /**
     * @return Expression
     */
    public function expression()
    {
        return $this->manager->getDriver()->getQueryBuilder()->expression();
    }

    public function get($primaryKey)
    {
        return $this->find()->getByPrimaryKey(func_get_args());
    }

    public function delete($object)
    {
        $queryBuilder = $this->manager->getDriver()->getQueryBuilder();

        foreach ($this->metadata->getRelations() as $relation) {
            $foreignKey    = $this->metadata->getFieldValue($object, $relation->foreignKey);
            $relatedEntity = $this->manager->get($relation->target);
            switch ($relation->type) {
                case Relation::HAS_ONE:
                case Relation::HAS_MANY:
                    $relatedEntity->find()->deleteByField($relation->targetKey, $foreignKey);
                    break;

                case Relation::MANY_MANY:
                    $queryBuilder
                        ->delete($relation->joinTable)
                        ->where(
                            $queryBuilder->expression()->eq(
                                $this->metadata->getTable() . '_' . $relation->foreignKey,
                                $queryBuilder->createPositionalParameter($foreignKey)
                            )
                        )->query();
                    break;

                case Relation::BELONGS_TO:
                    //don't delete the record this one belongs to
                    break;
            }
        }

        if ($this->isPrimaryKeySet($object)) {
            $queryBuilder->delete($this->metadata->getTable())
                ->where(
                    $queryBuilder->expression()->eq(
                        $this->metadata->getPrimaryKey(),
                        $queryBuilder->createPositionalParameter(
                            $this->getPrimaryKeyValue($object)
                        )
                    )
                )->query();
            unset($this->relatedObjectHandles[spl_object_hash($object)]);
        }
    }

    public function save($object)
    {
        $this->metadata->assertObjectInstance($object);
        $objectId = spl_object_hash($object);

        if (!isset($this->objectStates[$objectId]) || $this->relatedObjectHandles[$objectId] !== $object) {
            $this->objectStates[$objectId]         = self::STATE_NEW;
            $this->originalData[$objectId]         = [];
            $this->objectRelations[$objectId]      = $this->createEmptyRelationsArray();
            $this->relatedObjectHandles[$objectId] = $object;
        } elseif (isset($this->readOnlyObjectHandles[$objectId])) {
            return;
        }

        $relationsIterator = new \ArrayIterator($this->metadata->getRelations());

        $hasOneRelations   = new \CallbackFilterIterator(
            $relationsIterator,
            function (Relation $relation) {
                return $relation->type === Relation::HAS_ONE || $relation->type === Relation::BELONGS_TO;
            });
        $hasManyRelations  = new \CallbackFilterIterator(
            $relationsIterator,
            function (Relation $relation) {
                return $relation->type === Relation::HAS_MANY;
            });
        $manyManyRelations = new \CallbackFilterIterator(
            $relationsIterator,
            function (Relation $relation) {
                return $relation->type === Relation::MANY_MANY;
            });

        $modifiedManyManyRelations = [];
        foreach ($manyManyRelations as $relationName => $relation) {
            $relatedEntity  = $this->manager->get($relation->target);
            $relatedObjects = $this->metadata->getRelationValue($object, $relationName);

            $currentForeignKeys = array_map(
                function ($object) use ($relatedEntity) {
                    if (!is_object($object)) {
                        //the foreign key is set directly
                        return $object;
                    }
                    $relatedEntity->save($object);

                    return $relatedEntity->getPrimaryKeyValue($object);
                },
                $relatedObjects
            );

            $originalForeignKeys = $this->objectRelations[$objectId][$relationName];

            $modifiedManyManyRelations[$relationName] = [
                'deleted'  => array_diff($originalForeignKeys, $currentForeignKeys),
                'inserted' => array_diff($currentForeignKeys, $originalForeignKeys)
            ];
{'fraction_non_alphanumeric': 0.1288148387792516, 'fraction_numerical': 0.00021567993098242207, 'mean_word_length': 1.8648439913500154, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 265, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3985387', 'n_tokens_mistral': 4853, 'n_tokens_neox': 4566, 'n_words': 786}