zanui/ZanuiFixturesBundle

View on GitHub
src/DataFixtures/ZanuiFixture.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php
/**
 * Zanui (http://www.zanui.com.au/)
 *
 * @link      http://github.com/zanui/shop for the canonical source repository
 * @copyright Copyright (c) 2011-2014 Internet Services Australia 3 Pty Limited (http://www.zanui.com.au)
 * @license   The MIT License (MIT)
 */

namespace Zanui\FixturesBundle\DataFixtures;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Zanui\FixturesBundle\Exception\InvalidOptionException;
use Zanui\FixturesBundle\Exception\LoadInfoException;

/**
 * Zanui fixture
 *
 * This is the base class for all our fixtures,
 * and makes them container aware and ordered.
 */
abstract class ZanuiFixture extends AbstractFixture implements FixtureInterface, OrderedFixtureInterface, ContainerAwareInterface
{
    /**
     * Indicates whether the entities should be saved overriding
     * the default generation type to preserve the given IDs
     */
    const DATA_OPTION_FLUSH_PRESERVING_IDS = 'flush_preserving_ids';

    /**
     * Whether to set a reference for the current entity
     */
    const DATA_OPTION_ADD_REFERENCE = 'add_reference';

    /**
     * List of fields that should be considered foreign keys.
     * All fields starting with fk_ are considered foreign keys by default,
     * so they don't need to be included in the list.
     */
    const DATA_OPTION_FOREIGN_KEYS = 'foreign_keys';

    /**
     * List of fields that point to local references.
     * This is necessary to simplify custom loaders.
     */
    const DATA_OPTION_LOCAL_REFERENCES = 'local_references';

    /**
     * Whether the entity should be flushed on every row instead of only at the end.
     * Necessary for CatalogCategory due to the nested set behaviour.
     */
    const DATA_OPTION_FLUSH_ON_EVERY_ROW = 'flush_on_every_row';

    /**
     * List of fields which values should be transformed to DateTime.
     */
    const DATA_OPTION_DATETIME_FIELDS = 'date_time_fields';

    /**
     * Defines the separator to use for the local reference suffix
     */
    const LOCAL_REFERENCE_SEPARATOR = '-';

    /** @var string The namespace to use when creating entities */
    protected $namespace;

    /** @var string The name of the fixture */
    protected $name;

    /** @var int The order in which the fixture should be loaded */
    protected $order;

    /** @var string The base directory for the fixture. The data will be loaded from the Data subdirectory. */
    protected $baseDir;

    /** @var array */
    protected $info;

    /** @var ContainerInterface */
    protected $container;

    /** @var mixed */
    protected $entity;

    /** @var array Available data options */
    protected $dataOptions = array(
        self::DATA_OPTION_FLUSH_PRESERVING_IDS,
        self::DATA_OPTION_ADD_REFERENCE,
        self::DATA_OPTION_FOREIGN_KEYS,
        self::DATA_OPTION_LOCAL_REFERENCES,
        self::DATA_OPTION_FLUSH_ON_EVERY_ROW,
        self::DATA_OPTION_DATETIME_FIELDS
    );

    /**
     * Loads fixture info.
     *
     * @throws LoadInfoException
     */
    abstract public function loadInfo();

    /**
     * @param string $namespace
     *
     * @return ZanuiFixture
     */
    public function setNamespace($namespace)
    {
        $this->namespace = $namespace;

        return $this;
    }

    /**
     * @return string
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * {@inheritDoc}
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * @return object
     */
    public function getEntity()
    {
        return $this->entity;
    }

    /**
     * @return array
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * {@inheritDoc}
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;

        if (!isset($this->namespace) && $this->container->hasParameter('entity_namespace_fallback')) {
            $this->namespace = $this->container->getParameter('entity_namespace_fallback');
        }

        if (!isset($this->order) && $this->container->hasParameter('base_order_fallback')) {
            $this->order = $this->container->getParameter('base_order_fallback');
        }
    }

    /**
     * Sets a field of a given entity
     *
     * @param mixed  $entity
     * @param string $fieldName
     * @param string $fieldValue
     * @param array  $options
     */
    public function setField($entity, $fieldName, $fieldValue, array $options = array())
    {
        $setterName = $this->fieldToSetterMethod($fieldName);

        if ($this->isForeignKey($fieldName, $options)) {
            $entity->$setterName($this->getReference($fieldValue));
        } else {
            $entity->$setterName($this->processValue($fieldName, $fieldValue, $options));
        }

        $this->entity = $entity;
    }

    /**
     * Process a value and transforms if needed
     *
     * @param string $fieldName
     * @param string $fieldValue
     * @param array  $options
     *
     * @return mixed
     */
    public function processValue($fieldName, $fieldValue, $options)
    {
        if ($this->isDateTimeField($fieldName, $options)) {
            return new \DateTime($fieldValue);
        }

        return $fieldValue;
    }

    /**
     * Asserts whether the given option is valid or not
     *
     * @param string $optionName
     *
     * @return bool
     */
    public function isValidDataOption($optionName)
    {
        return in_array($optionName, $this->dataOptions);
    }

    /**
     * Returns true if an option is enabled and false otherwise
     *
     * @param string $optionName
     * @param array  $options
     *
     * @return bool
     * @throws InvalidOptionException
     */
    public function isOptionEnabled($optionName, array $options)
    {
        if (!$this->isValidDataOption($optionName)) {
            throw new InvalidOptionException('"' . $optionName . '" is not a valid option.');
        }

        return (!empty($options[$optionName]) && $options[$optionName] === true);
    }

    /**
     * Returns true if an option is disabled and false otherwise
     *
     * @param string $optionName
     * @param array  $options
     *
     * @return bool
     */
    public function isOptionDisabled($optionName, array $options)
    {
        return !$this->isOptionEnabled($optionName, $options);
    }

    /**
     * Converts a field name to a setter method name
     * (eg. name -> setName, name_en -> setNameEn)
     *
     * @param string $fieldName
     *
     * @return string
     */
    public function fieldToSetterMethod($fieldName)
    {
        return 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $fieldName)));
    }

    /**
     * Returns true if a field is a foreign key and false otherwise.
     *
     * @param string $fieldName
     * @param array  $options
     *
     * @return bool
     */
    public function isForeignKey($fieldName, array $options = array())
    {
        if (strncasecmp('fk_', $fieldName, 3) === 0) {
            return true;
        }

        return (
            !empty($options[static::DATA_OPTION_FOREIGN_KEYS]) &&
            in_array($fieldName, $options[static::DATA_OPTION_FOREIGN_KEYS])
        );
    }

    /**
     * Returns true if a field is of type datetime
     *
     * @param string $fieldName
     * @param array  $options
     *
     * @return bool
     */
    public function isDateTimeField($fieldName, array $options = array())
    {
        return $this->isOfDataOption($fieldName, static::DATA_OPTION_DATETIME_FIELDS, $options);
    }

    /**
     * Returns true if a field is a foreign key and false otherwise.
     *
     * @param string $fieldName
     * @param array  $options
     *
     * @return bool
     */
    public function isLocalReference($fieldName, array $options = array())
    {
        return $this->isOfDataOption($fieldName, static::DATA_OPTION_LOCAL_REFERENCES, $options);
    }

    /**
     * Returns true if a field is declared as being of a given data option.
     *
     * @param string $fieldName
     * @param string $optionName
     * @param array  $options
     *
     * @return bool
     */
    protected function isOfDataOption($fieldName, $optionName, array $options = array())
    {
        return (!empty($options[$optionName]) && in_array($fieldName, $options[$optionName]));
    }

    /**
     * @param array $data
     *
     * @return array
     */
    public function getOptions(array $data)
    {
        return isset($data['options']) ? $data['options'] : array();
    }

    /**
     * Loads all rows for a specific entity
     *
     * @param string        $entityClass
     * @param string        $key
     * @param ObjectManager $manager
     * @param array         $info
     * @param string        $referenceUniqueSuffix
     */
    public function loadEntity($entityClass, $key, $manager, $info, $referenceUniqueSuffix = '')
    {
        $options = $this->getOptions($info);

        foreach ($info['data'] as $itemIndex => $itemData) {
            $this->loadSingleEntity(
                $entityClass,
                $key,
                $manager,
                $itemIndex,
                $itemData,
                $options,
                $referenceUniqueSuffix
            );
        }
    }

    /**
     * Loads a single row for a specific entity
     *
     * @param string        $entityClass
     * @param string        $key
     * @param ObjectManager $manager
     * @param int           $itemIndex
     * @param array         $itemData
     * @param array         $options
     * @param string        $referenceUniqueSuffix
     */
    public function loadSingleEntity(
        $entityClass,
        $key,
        ObjectManager $manager,
        $itemIndex,
        $itemData,
        $options,
        $referenceUniqueSuffix = ''
    ) {
        $entity = new $entityClass();

        foreach ($itemData as $fieldName => $fieldValue) {
            $value = $fieldValue;

            if ($this->isLocalReference($fieldName, $options)) {
                $value .= $referenceUniqueSuffix;
            }

            $this->setField($entity, $fieldName, $value, $options);
        }

        $manager->persist($entity);

        if ($this->isOptionEnabled(static::DATA_OPTION_FLUSH_ON_EVERY_ROW, $options)) {
            $this->flush($manager, $entityClass, $options);
        }

        if ($this->isOptionEnabled(static::DATA_OPTION_ADD_REFERENCE, $options)) {
            $referenceKey = $this->getReferenceKey($key, $itemIndex, $referenceUniqueSuffix);
            $this->addReference($referenceKey, $entity);
        }

        $this->entity = $entity;
    }

    /**
     * @param ObjectManager $manager
     * @param string        $entityClass Fully qualified class name (ie. with namespace)
     * @param array         $options
     */
    public function flush(ObjectManager $manager, $entityClass, array $options)
    {
        if ($this->isOptionEnabled(static::DATA_OPTION_FLUSH_PRESERVING_IDS, $options)) {
            $this->flushPreservingIds($manager, $entityClass);
        } else {
            $manager->flush();
        }
    }

    /**
     * Triggers the DB write operation overriding the entity's generator type.
     * This is necessary for table entries for which part of our logic assumes have a certain ID.
     * (eg. sales order item statuses)
     *
     * @param ObjectManager $manager
     * @param string        $className Fully qualified class name (ie. with namespace)
     */
    public function flushPreservingIds(ObjectManager $manager, $className)
    {
        /** @var ClassMetadata $metadata */
        $metadata = $manager->getClassMetaData($className);

        $defaultGenerator = $metadata->generatorType;

        $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);

        $manager->flush();

        // set generator type back to the default
        $metadata->setIdGeneratorType($defaultGenerator);
    }

    /**
     * @param string $key
     * @param string $itemIndex
     * @param string $referenceUniqueSuffix
     *
     * @return string
     */
    public function getReferenceKey($key, $itemIndex, $referenceUniqueSuffix)
    {
        return implode(
            static::LOCAL_REFERENCE_SEPARATOR,
            array_filter(
                array($key, $itemIndex),
                function ($value) {
                    return ($value !== null && $value !== false && $value !== '');
                }
            )
        ) .
        $referenceUniqueSuffix;
    }

    /**
     * Returns a unique suffix for local references
     *
     * @return string
     */
    public function generateUniqueSuffix()
    {
        return static::LOCAL_REFERENCE_SEPARATOR . uniqid();
    }
}