<commit_before><?php
/**
 * Webiny Framework (http://www.webiny.com/framework)
 *
 * @copyright Copyright Webiny LTD
 */

namespace Webiny\Component\Entity;

use Webiny\Component\Entity\Attribute\AttributeAbstract;
use Webiny\Component\Entity\Attribute\AttributeType;
use Webiny\Component\Entity\Attribute\CharAttribute;
use Webiny\Component\Entity\Attribute\Many2ManyAttribute;
use Webiny\Component\Entity\Attribute\One2ManyAttribute;
use Webiny\Component\Entity\Attribute\ValidationException;
use Webiny\Component\Entity\AttributeStorage\Many2ManyStorage;
use Webiny\Component\StdLib\StdLibTrait;
use Webiny\Component\StdLib\StdObject\ArrayObject\ArrayObject;

/**
 * Entity
 * @package \Webiny\Component\Entity
 */
abstract class EntityAbstract implements \ArrayAccess
{
    use StdLibTrait, EntityTrait;

    /**
     * Entity attributes
     * @var ArrayObject
     */
    protected $attributes;

    /**
     * @var string Entity collection name
     */
    protected static $entityCollection = null;

    /**
     * View mask (used for grids and many2one input fields)
     * @var string
     */
    protected static $entityMask = '{id}';

    private $dirty = false;

    /**
     * This method is called during instantiation to build entity structure
     * @return void
     */
    protected abstract function entityStructure();

    /**
     * Find entity by ID
     *
     * @param $id
     *
     * @return null|EntityAbstract
     */
    public static function findById($id)
    {
        if (!$id || strlen($id) != 24) {
            return null;
        }
        $instance = static::entity()->get(get_called_class(), $id);
        if ($instance) {
            return $instance;
        }
        $data = static::entity()->getDatabase()->findOne(static::$entityCollection, ['id' => $id]);
        if (!$data) {
            return null;
        }
        $instance = new static;
        $instance->populate($data)->setDirty(false);

        return static::entity()->add($instance);
    }

    /**
     * Count records using given criteria
     *
     * @param array $conditions
     *
     * @return int
     *
     */
    public static function count(array $conditions = [])
    {
        return static::entity()->getDatabase()->count(static::$entityCollection, $conditions);
    }

    /**
     * Find entity by array of conditions
     *
     * @param array $conditions
     *
     * @return null|EntityAbstract
     * @throws EntityException
     */
    public static function findOne(array $conditions = [])
    {
        $data = static::entity()->getDatabase()->findOne(static::$entityCollection, $conditions);
        if (!$data) {
            return null;
        }
        $instance = new static;
        $instance->populate($data)->setDirty(false);

        return static::entity()->add($instance);
    }

    /**
     * Find entities
     *
     * @param mixed $conditions
     *
     * @param array $order Example: ['-name', '+title']
     * @param int   $limit
     * @param int   $page
     *
     * @return EntityCollection
     */
    public static function find(array $conditions = [], array $order = [], $limit = 0, $page = 0)
    {
        /**
         * Convert order parameters to Mongo format
         */
        $order = self::parseOrderParameters($order);
        $offset = $limit * ($page > 0 ? $page - 1 : 0);

        return new EntityCollection(get_called_class(), static::$entityCollection, $conditions, $order, $limit, $offset);
    }

    /**
     * Entity constructor
     */
    public function __construct()
    {
        $this->attributes = $this->arr();
        /**
         * Add ID to the list of attributes
         */
        $this->attr('id')->char();

        $this->entityStructure();
    }

    /**
     * @param $attribute
     *
     * @return EntityAttributeBuilder
     */
    public function attr($attribute)
    {
        return EntityAttributeBuilder::getInstance()->setContext($this->attributes, $attribute)->setEntity($this);
    }

    /**
     * Convert EntityAbstract to array with specified fields.
     * If no fields are specified, array will contain all simple and Many2One attributes
     *
     * @param string $fields      List of fields to extract
     *
     * @param int    $nestedLevel How many levels to extract (Default: 1, means SELF + 1 level)
     *
     * @return array
     */
    public function toArray($fields = '', $nestedLevel = 1)
    {
        $dataExtractor = new EntityDataExtractor($this, $nestedLevel);

        return $dataExtractor->extractData($fields);
    }


    /**
     * Return string representation of entity
     * @return mixed
     */
    public function __toString()
    {
        return $this->getMaskedValue();
    }

    /**
     * Is this entity already saved?
     *
     * @return bool
     */
    public function exists(){
        return $this->id !== null;
    }

    /**
     * Set entity's dirty flag
     *
     * NOTE: you should not be calling this method on your own!
     * This me
     *
     * @param bool $flag
     *
     * @return $this|bool
     */
    public function setDirty($flag = true)
    {
        $this->dirty = boolval($flag);

        return $this;
    }

    /**
     * Get entity's dirty flag
     *
     * @return bool
     */
    public function getDirty()
    {
        return $this->dirty;
    }

    /**
     * Get entity attribute
     *
     * @param string $attribute
     *
     * @throws EntityException
     * @return AttributeAbstract
     */
    public function getAttribute($attribute)
    {
        if (!$this->attributes->keyExists($attribute)) {
            throw new EntityException(EntityException::ATTRIBUTE_NOT_FOUND, [
                $attribute,
                get_class($this)
            ]);
        }

        return $this->attributes[$attribute];
    }

    /**
     * Get all entity attributes
     *
     * @return ArrayObject
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * Get entity ID
     * @return CharAttribute
     */
    public function getId()
    {
        return $this->attributes['id'];
    }

    public function getMaskedValue()
    {
        $maskItems = [];
        preg_match_all('/\{(.*?)\}/', static::$entityMask, $maskItems);
        $maskedValue = $this->str(static::$entityMask);
        foreach ($maskItems[1] as $attr) {
            $maskedValue->replace('{' . $attr . '}', $this->getAttribute($attr)->getValue());
        }

        return $maskedValue->val();
    }

    /**
     * Save entity attributes to database
     */
    public function save()
    {
        if (!$this->getDirty() && $this->exists()) {
            return true;
        }

        $data = [];
        foreach ($this->getAttributes() as $key => $attr) {
            if (!$this->isInstanceOf($attr, AttributeType::ONE2MANY) && !$this->isInstanceOf($attr,
                    AttributeType::MANY2MANY)
            ) {
                $data[$key] = $attr->getDbValue();
            }
        }

        /**
         * Insert or update
         */
        if (!$this->exists()) {
            $data['_id'] = new \MongoId();
            $data['id'] = (string)$data['_id'];
            $this->entity()->getDatabase()->insert(static::$entityCollection, $data);
            $this->id = $data['id'];
        } else {
            $where = ['_id' => new \MongoId($this->id)];
            $this->entity()
                 ->getDatabase()
                 ->update(static::$entityCollection, $where, ['$set' => $data], ['upsert' => true]);
        }

        $this->setDirty(false);

        /**
         * Now save One2Many values
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr One2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::ONE2MANY)) {
                foreach ($attr->getValue() as $item) {
                    $item->getAttribute($attr->getRelatedAttribute())->setValue($this);
                    $item->save();
                }
                /**
                 * The value of one2many attribute must be set to null to trigger data reload on next access.
                 * This is necessary when we have circular references, and parent record does not get it's many2one ID saved
                 * until all child referenced objects are saved. Only then can we get proper links between referenced classes.
                 */
                $attr->setValue(null);
            }
        }

        /**
         * Now save Many2Many values
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr Many2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                Many2ManyStorage::getInstance()->save($attr);
            }
        }

        return true;
    }

    /**
     * Delete entity
     * @return bool
     * @return bool
     * @throws EntityException
     */
    public function delete()
    {
        /**
         * Check for many2many attributes and make sure related Entity has a corresponding many2many attribute defined.
         * If not - deleting is not allowed.
         */

        /* @var $attr Many2ManyAttribute */
        $thisClass = '\\' . get_class($this);
        foreach ($this->getAttributes() as $attrName => $attr) {
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                $foundMatch = false;
                $relatedClass = $attr->getEntity();
                $relatedEntity = new $relatedClass;
                /* @var $relAttr Many2ManyAttribute */
                foreach ($relatedEntity->getAttributes() as $relAttr) {
                    if ($this->isInstanceOf($relAttr, AttributeType::MANY2MANY) && $relAttr->getEntity() == $thisClass
                    ) {
                        $foundMatch = true;
                    }
                }

                if (!$foundMatch) {
                    throw new EntityException(EntityException::NO_MATCHING_MANY2MANY_ATTRIBUTE_FOUND, [
                        $thisClass,
                        $relatedClass,
                        $attrName
                    ]);
                }
            }
        }

        /**
         * First check all one2many records to see if deletion is restricted
         */
        $deleteAttributes = [];
        foreach ($this->getAttributes() as $key => $attr) {
            if ($this->isInstanceOf($attr, AttributeType::ONE2MANY)) {
                /* @var $attr One2ManyAttribute */
                if ($attr->getOnDelete() == 'restrict' && $this->getAttribute($key)->getValue()->count() > 0) {
                    throw new EntityException(EntityException::ENTITY_DELETION_RESTRICTED, [$key]);
                }
                $deleteAttributes[] = $key;
            }
        }

        /**
         * Delete many2many records
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr Many2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                $firstClassName = $this->extractClassName($attr->getParentEntity());
                $query = [$firstClassName => $this->id];
                $this->entity()->getDatabase()->remove($attr->getIntermediateCollection(), $query);
            }
        }

        /**
         * Delete one2many records
         */
        foreach ($deleteAttributes as $attr) {
            foreach ($this->getAttribute($attr)->getValue() as $item) {
                $item->delete();
            }
        }

        /**
         * Delete $this
         */
        $this->entity()
             ->getDatabase()
             ->remove(static::$entityCollection,
                 ['_id' => $this->entity()->getDatabase()->id($this->id)]);

        static::entity()->remove($this);

        return true;
    }

    /**
     * Populate entity with given data
     *
     * @param array $data
     *
     * @throws EntityException
     * @return $this
     */
    public function populate($data)
    {
        $entityCollectionClass = '\Webiny\Component\Entity\EntityCollection';
        $validation = $this->arr();
        /* @var $entityAttribute AttributeAbstract */
        foreach ($this->attributes as $attributeName => $entityAttribute) {
            /**
             * Check if attribute is required and it's value is set.
             */
            if ($entityAttribute->getRequired() && !isset($data[$attributeName]) && !$this->exists()) {
                $validation[$attributeName] = new ValidationException(ValidationException::REQUIRED_ATTRIBUTE_IS_MISSING,
                    [$attributeName]);
                continue;
            }

            /**
             * In case it is an update - if the attribute is not in new $data, it's no big deal, we already have the previous value.
             */
            if(!isset($data[$attributeName]) && $this->exists()){
                continue;
            }

            $canPopulate = !$this->exists() || !$entityAttribute->getOnce();
            if (isset($data[$attributeName]) && $canPopulate) {
                $dataValue = $data[$attributeName];
                $isOne2Many = $this->isInstanceOf($entityAttribute, AttributeType::ONE2MANY);
                $isMany2Many = $this->isInstanceOf($entityAttribute, AttributeType::MANY2MANY);
                $isMany2One = $this->isInstanceOf($entityAttribute, AttributeType::MANY2ONE);

                if ($isMany2One) {
                    try {
                        $entityAttribute->validate($dataValue)->setValue($dataValue);
                    } catch (ValidationException $e) {
                        $validation[$attributeName] = $e;
                        continue;
                    }
                } elseif ($isOne2Many) {
                    $entityClass = $entityAttribute->getEntity();

                    // Validate One2Many attribute value
                    if (!$this->isArray($dataValue) && !$this->isArrayObject($dataValue) && !$this->isInstanceOf($dataValue,
                            $entityCollectionClass)
                    ) {
                        $validation[$attributeName] = new ValidationException(ValidationException::ATTRIBUTE_VALIDATION_FAILED,
                            [
                                $attributeName,
                                'array, ArrayObject or EntityCollection',
                                gettype($dataValue)
                            ]);
                        continue;<commit_msg>Fixed problem with once() attributes on populate from DB.<commit_after><?php
/**
 * Webiny Framework (http://www.webiny.com/framework)
 *
 * @copyright Copyright Webiny LTD
 */

namespace Webiny\Component\Entity;

use Webiny\Component\Entity\Attribute\AttributeAbstract;
use Webiny\Component\Entity\Attribute\AttributeType;
use Webiny\Component\Entity\Attribute\CharAttribute;
use Webiny\Component\Entity\Attribute\Many2ManyAttribute;
use Webiny\Component\Entity\Attribute\One2ManyAttribute;
use Webiny\Component\Entity\Attribute\ValidationException;
use Webiny\Component\Entity\AttributeStorage\Many2ManyStorage;
use Webiny\Component\StdLib\StdLibTrait;
use Webiny\Component\StdLib\StdObject\ArrayObject\ArrayObject;

/**
 * Entity
 * @package \Webiny\Component\Entity
 */
abstract class EntityAbstract implements \ArrayAccess
{
    use StdLibTrait, EntityTrait;

    /**
     * Entity attributes
     * @var ArrayObject
     */
    protected $attributes;

    /**
     * @var string Entity collection name
     */
    protected static $entityCollection = null;

    /**
     * View mask (used for grids and many2one input fields)
     * @var string
     */
    protected static $entityMask = '{id}';

    private $dirty = false;

    /**
     * This method is called during instantiation to build entity structure
     * @return void
     */
    protected abstract function entityStructure();

    /**
     * Find entity by ID
     *
     * @param $id
     *
     * @return null|EntityAbstract
     */
    public static function findById($id)
    {
        if (!$id || strlen($id) != 24) {
            return null;
        }
        $instance = static::entity()->get(get_called_class(), $id);
        if ($instance) {
            return $instance;
        }
        $data = static::entity()->getDatabase()->findOne(static::$entityCollection, ['id' => $id]);
        if (!$data) {
            return null;
        }
        $instance = new static;
        $data['__webiny_db__'] = true;
        $instance->populate($data)->setDirty(false);

        return static::entity()->add($instance);
    }

    /**
     * Count records using given criteria
     *
     * @param array $conditions
     *
     * @return int
     *
     */
    public static function count(array $conditions = [])
    {
        return static::entity()->getDatabase()->count(static::$entityCollection, $conditions);
    }

    /**
     * Find entity by array of conditions
     *
     * @param array $conditions
     *
     * @return null|EntityAbstract
     * @throws EntityException
     */
    public static function findOne(array $conditions = [])
    {
        $data = static::entity()->getDatabase()->findOne(static::$entityCollection, $conditions);
        if (!$data) {
            return null;
        }
        $instance = new static;
        $data['__webiny_db__'] = true;
        $instance->populate($data)->setDirty(false);

        return static::entity()->add($instance);
    }

    /**
     * Find entities
     *
     * @param mixed $conditions
     *
     * @param array $order Example: ['-name', '+title']
     * @param int   $limit
     * @param int   $page
     *
     * @return EntityCollection
     */
    public static function find(array $conditions = [], array $order = [], $limit = 0, $page = 0)
    {
        /**
         * Convert order parameters to Mongo format
         */
        $order = self::parseOrderParameters($order);
        $offset = $limit * ($page > 0 ? $page - 1 : 0);

        return new EntityCollection(get_called_class(), static::$entityCollection, $conditions, $order, $limit, $offset);
    }

    /**
     * Entity constructor
     */
    public function __construct()
    {
        $this->attributes = $this->arr();
        /**
         * Add ID to the list of attributes
         */
        $this->attr('id')->char();

        $this->entityStructure();
    }

    /**
     * @param $attribute
     *
     * @return EntityAttributeBuilder
     */
    public function attr($attribute)
    {
        return EntityAttributeBuilder::getInstance()->setContext($this->attributes, $attribute)->setEntity($this);
    }

    /**
     * Convert EntityAbstract to array with specified fields.
     * If no fields are specified, array will contain all simple and Many2One attributes
     *
     * @param string $fields      List of fields to extract
     *
     * @param int    $nestedLevel How many levels to extract (Default: 1, means SELF + 1 level)
     *
     * @return array
     */
    public function toArray($fields = '', $nestedLevel = 1)
    {
        $dataExtractor = new EntityDataExtractor($this, $nestedLevel);

        return $dataExtractor->extractData($fields);
    }


    /**
     * Return string representation of entity
     * @return mixed
     */
    public function __toString()
    {
        return $this->getMaskedValue();
    }

    /**
     * Is this entity already saved?
     *
     * @return bool
     */
    public function exists(){
        return $this->id !== null;
    }

    /**
     * Set entity's dirty flag
     *
     * NOTE: you should not be calling this method on your own!
     * This me
     *
     * @param bool $flag
     *
     * @return $this|bool
     */
    public function setDirty($flag = true)
    {
        $this->dirty = boolval($flag);

        return $this;
    }

    /**
     * Get entity's dirty flag
     *
     * @return bool
     */
    public function getDirty()
    {
        return $this->dirty;
    }

    /**
     * Get entity attribute
     *
     * @param string $attribute
     *
     * @throws EntityException
     * @return AttributeAbstract
     */
    public function getAttribute($attribute)
    {
        if (!$this->attributes->keyExists($attribute)) {
            throw new EntityException(EntityException::ATTRIBUTE_NOT_FOUND, [
                $attribute,
                get_class($this)
            ]);
        }

        return $this->attributes[$attribute];
    }

    /**
     * Get all entity attributes
     *
     * @return ArrayObject
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * Get entity ID
     * @return CharAttribute
     */
    public function getId()
    {
        return $this->attributes['id'];
    }

    public function getMaskedValue()
    {
        $maskItems = [];
        preg_match_all('/\{(.*?)\}/', static::$entityMask, $maskItems);
        $maskedValue = $this->str(static::$entityMask);
        foreach ($maskItems[1] as $attr) {
            $maskedValue->replace('{' . $attr . '}', $this->getAttribute($attr)->getValue());
        }

        return $maskedValue->val();
    }

    /**
     * Save entity attributes to database
     */
    public function save()
    {
        if (!$this->getDirty() && $this->exists()) {
            return true;
        }

        $data = [];
        foreach ($this->getAttributes() as $key => $attr) {
            if (!$this->isInstanceOf($attr, AttributeType::ONE2MANY) && !$this->isInstanceOf($attr,
                    AttributeType::MANY2MANY)
            ) {
                $data[$key] = $attr->getDbValue();
            }
        }

        /**
         * Insert or update
         */
        if (!$this->exists()) {
            $data['_id'] = new \MongoId();
            $data['id'] = (string)$data['_id'];
            $this->entity()->getDatabase()->insert(static::$entityCollection, $data);
            $this->id = $data['id'];
        } else {
            $where = ['_id' => new \MongoId($this->id)];
            $this->entity()
                 ->getDatabase()
                 ->update(static::$entityCollection, $where, ['$set' => $data], ['upsert' => true]);
        }

        $this->setDirty(false);

        /**
         * Now save One2Many values
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr One2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::ONE2MANY)) {
                foreach ($attr->getValue() as $item) {
                    $item->getAttribute($attr->getRelatedAttribute())->setValue($this);
                    $item->save();
                }
                /**
                 * The value of one2many attribute must be set to null to trigger data reload on next access.
                 * This is necessary when we have circular references, and parent record does not get it's many2one ID saved
                 * until all child referenced objects are saved. Only then can we get proper links between referenced classes.
                 */
                $attr->setValue(null);
            }
        }

        /**
         * Now save Many2Many values
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr Many2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                Many2ManyStorage::getInstance()->save($attr);
            }
        }

        return true;
    }

    /**
     * Delete entity
     * @return bool
     * @return bool
     * @throws EntityException
     */
    public function delete()
    {
        /**
         * Check for many2many attributes and make sure related Entity has a corresponding many2many attribute defined.
         * If not - deleting is not allowed.
         */

        /* @var $attr Many2ManyAttribute */
        $thisClass = '\\' . get_class($this);
        foreach ($this->getAttributes() as $attrName => $attr) {
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                $foundMatch = false;
                $relatedClass = $attr->getEntity();
                $relatedEntity = new $relatedClass;
                /* @var $relAttr Many2ManyAttribute */
                foreach ($relatedEntity->getAttributes() as $relAttr) {
                    if ($this->isInstanceOf($relAttr, AttributeType::MANY2MANY) && $relAttr->getEntity() == $thisClass
                    ) {
                        $foundMatch = true;
                    }
                }

                if (!$foundMatch) {
                    throw new EntityException(EntityException::NO_MATCHING_MANY2MANY_ATTRIBUTE_FOUND, [
                        $thisClass,
                        $relatedClass,
                        $attrName
                    ]);
                }
            }
        }

        /**
         * First check all one2many records to see if deletion is restricted
         */
        $deleteAttributes = [];
        foreach ($this->getAttributes() as $key => $attr) {
            if ($this->isInstanceOf($attr, AttributeType::ONE2MANY)) {
                /* @var $attr One2ManyAttribute */
                if ($attr->getOnDelete() == 'restrict' && $this->getAttribute($key)->getValue()->count() > 0) {
                    throw new EntityException(EntityException::ENTITY_DELETION_RESTRICTED, [$key]);
                }
                $deleteAttributes[] = $key;
            }
        }

        /**
         * Delete many2many records
         */
        foreach ($this->getAttributes() as $attr) {
            /* @var $attr Many2ManyAttribute */
            if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
                $firstClassName = $this->extractClassName($attr->getParentEntity());
                $query = [$firstClassName => $this->id];
                $this->entity()->getDatabase()->remove($attr->getIntermediateCollection(), $query);
            }
        }

        /**
         * Delete one2many records
         */
        foreach ($deleteAttributes as $attr) {
            foreach ($this->getAttribute($attr)->getValue() as $item) {
                $item->delete();
            }
        }

        /**
         * Delete $this
         */
        $this->entity()
             ->getDatabase()
             ->remove(static::$entityCollection,
                 ['_id' => $this->entity()->getDatabase()->id($this->id)]);

        static::entity()->remove($this);

        return true;
    }

    /**
     * Populate entity with given data
     *
     * @param array $data
     *
     * @throws EntityException
     * @return $this
     */
    public function populate($data)
    {
        $fromDb = false;
        if(isset($data['__webiny_db__'])){
            $fromDb = true;
        }

        $entityCollectionClass = '\Webiny\Component\Entity\EntityCollection';
        $validation = $this->arr();
        /* @var $entityAttribute AttributeAbstract */
        foreach ($this->attributes as $attributeName => $entityAttribute) {
            /**
             * Check if attribute is required and it's value is set.
             */
            if ($entityAttribute->getRequired() && !isset($data[$attributeName]) && !$this->exists()) {
                $validation[$attributeName] = new ValidationException(ValidationException::REQUIRED_ATTRIBUTE_IS_MISSING,
                    [$attributeName]);
                continue;
            }

            /**
             * In case it is an update - if the attribute is not in new $data, it's no big deal, we already have the previous value.
             */
            if(!isset($data[$attributeName]) && $this->exists()){
                continue;
            }

            $canPopulate = !$this->exists() || $fromDb || !$entityAttribute->getOnce();
            if (isset($data[$attributeName]) && $canPopulate) {
                $dataValue = $data[$attributeName];
                $isOne2Many = $this->isInstanceOf($entityAttribute, AttributeType::ONE2MANY);
                $isMany2Many = $this->isInstanceOf($entityAttribute, AttributeType::MANY2MANY);
                $isMany2One = $this->isInstanceOf($entityAttribute, AttributeType::MANY2ONE);

                if ($isMany2One) {
                    try {
                        $entityAttribute->validate($dataValue)->setValue($dataValue);
                    } catch (ValidationException $e) {
                        $validation[$attributeName] = $e;
                        continue;
                    }
                } elseif ($isOne2Many) {
                    $entityClass = $entityAttribute->getEntity();

                    // Validate One2Many attribute value
                    if (!$this->isArray($dataValue) && !$this->isArrayObject($dataValue) && !$this->isInstanceOf($dataValue,
                            $entityCollectionClass)
                    ) {
                        $validation[$attributeName] = new ValidationException(ValidationException::ATTRIBUTE_VALIDATION_FAILED,
                            [
                                $attributeName,
                                'array, ArrayObject or EntityCollection',
                                gettype($dataValue)
                            ]);
                        continue;
{'fraction_non_alphanumeric': 0.13117157665030005, 'fraction_numerical': 0.0034779050736497546, 'mean_word_length': 1.7739525205712665, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 249, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '666921', 'n_tokens_mistral': 7747, 'n_tokens_neox': 7556, 'n_words': 1725}