<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Source;

use \Yucca\Component\Source\Exception\NoDataException;
use \Yucca\Component\Source\Exception\BreakSaveChainException;
use \Yucca\Component\Source\Exception\BreakRemoveChainException;
use Yucca\Component\Source\DataParser\DataParser;

/**
 * Class Chain
 * @package Yucca\Component\Source
 */
class Chain extends SourceAbstract
{
    /**
     * @var \Yucca\Component\Source\SourceAbstract[]
     */
    protected $sources;

    /**
     * @var \Yucca\Component\Source\DataParser\DataParser
     */
    protected $dataParser;

    /**
     * Chain constructor.
     *
     * @param string $sourceName
     * @param array  $configuration
     * @param array  $sources
     */
    public function __construct($sourceName, array $configuration = array(), $sources = array())
    {
        parent::__construct($sourceName, $configuration);

        if (empty($sources)) {
            throw new \InvalidArgumentException("\"sources\" must be a non empty array");
        }
        $this->sources = $sources;
    }

    /**
     * @param string $fieldName
     *
     * @return bool
     */
    public function canHandle($fieldName)
    {
        foreach ($this->sources as $source) {
            if ($source->canHandle($fieldName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param DataParser\DataParser $dataParser
     * @return DatabaseSingleRow
     */
    public function setDataParser(DataParser $dataParser)
    {
        $this->dataParser = $dataParser;

        return $this;
    }

    /**
     * @param array $identifier
     * @param bool  $rawData
     * @param mixed $shardingKey
     *
     * @return array
     * @throws NoDataException
     * @throws \Exception
     */
    public function load(array $identifier, $rawData, $shardingKey)
    {
        $sourcesToFeed = array();
        $datas = null;
        foreach ($this->sources as $sourceKey => $source) {
            try {
                $datas = $source->load($identifier, true, $shardingKey);
                break;
            } catch (NoDataException $exception) {
                $sourcesToFeed[] = $sourceKey;
            }
        }

        if (isset($datas)) {
            foreach ($sourcesToFeed as $sourceKey) {
                $this->sources[$sourceKey]->saveAfterLoading($datas, $identifier, $shardingKey);
            }

            return $this->dataParser->decode($datas, $this->configuration['fields']);
        }

        throw new NoDataException("Chain can't load datas for source {$this->sourceName} with ids : ".var_export($identifier, true));
    }

    /**
     * @param array $identifier
     * @param null  $shardingKey
     */
    public function remove(array $identifier, $shardingKey = null)
    {
        try {
            foreach ($this->sources as $source) {
                $source->remove($identifier, $shardingKey);
            }
        } catch (BreakRemoveChainException $e) {
        }
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     *
     * @throws \Exception
     */
    public function saveAfterLoading($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null)
    {
        throw new \Exception("Don't know what to do in chain {$this->sourceName}...");
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     *
     * @return array
     */
    public function save($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null)
    {
        $toReturn = array();
        try {
            foreach ($this->sources as $source) {
                $justCreated = $source->save($datas, $identifier, $shardingKey, $affectedRows);
                if (is_array($justCreated)) {
                    $toReturn = array_merge($toReturn, $justCreated);
                }
            }
        } catch (BreakSaveChainException $e) {
        }

        return $toReturn;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\SourceFactory;

use \Yucca\Component\Source\DataParser\DataParser;
use Yucca\Component\Source\PhpArray;

/**
 * Class PhpArrayFactory
 * @package Yucca\Component\SourceFactory
 */
class PhpArrayFactory implements SourceFactoryInterface
{
    /**
     * @var \Yucca\Component\Source\DataParser\DataParser
     */
    protected $dataParser;

    /**
     * @param \Yucca\Component\Source\DataParser\DataParser $dataParser
     */
    public function __construct(DataParser $dataParser)
    {
        $this->dataParser = $dataParser;
    }

    /**
     * Build source
     * @param string $sourceName
     * @param array  $params
     * @return \Yucca\Component\Source\Memcache
     */
    public function getSource($sourceName, array $params = array())
    {
        $toReturn = new PhpArray($sourceName, $params);
        $toReturn->setDataParser($this->dataParser);

        return $toReturn;
    }
}


<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 30.09.2015
 */

namespace skeeks\cms\admin\widgets;

use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;

/**
 * Class AdminPanelWidget
 * @package skeeks\cms\admin\widgets
 */
class AdminPanelWidget extends Widget
{
    /**
     * Widget options
     *
     *  'class' => 'sx-dashboard-widget',
     * 'data'      =>
     * [
     * 'id' => 1
     * ],
     * @var array
     */
    public $options = [];

    /**
     * Widget color scheme
     *
     * panel-primary
     * panel-success
     * panel-danger
     *
     * @var string
     */
    public $color = 'panel-primary';

    /**
     * Panel heading options
     *
     * @var array
     */
    public $headingOptions = [
        'class' => 'panel-heading'
    ];

    /**
     * Panel body options
     *
     * @var array
     */
    public $bodyOptions = [
        'class' => 'panel-body'
    ];

    /**
     * @var Название панели
     */
    public $name;
    /**
     * @var Содержимое
     */
    public $content;

    /**
     * @var Кнопки действий
     */
    public $actions;

    /**
     * Initializes the widget.
     * This renders the form open tag.
     */
    public function init()
    {
        Html::addCssClass($this->options, ['panel', 'sx-panel', $this->color]);

        $options = ArrayHelper::merge($this->options, [
            'id' => $this->id,
        ]);

        echo Html::beginTag('div', $options);

        echo Html::beginTag('div', $this->headingOptions);

        echo <<name}

                    {$this->actions}

HTML;

        echo Html::endTag('div');

        echo Html::beginTag('div', $this->bodyOptions);

        //echo '
' . $this->content;
        echo $this->content;

    }

    /**
     * Runs the widget.
     * This registers the necessary javascript code and renders the form close tag.
     * @throws InvalidCallException if `beginField()` and `endField()` calls are not matching
     */
    public function run()
    {
        echo Html::endTag('div');
        echo Html::endTag('div');

        self::registerJs();
    }

    static protected $_isRegisteredJs = null;

    static public function registerJs()
    {
        if (self::$_isRegisteredJs === true) {
            return false;
        }

        self::$_isRegisteredJs = true;

        \Yii::$app->view->registerJs(<<<JS
        $(".sx-btn-trigger-full").on('click', function()
        {
            var panel = $(this).closest('.sx-panel');
            if (panel.hasClass('sx-panel-full'))
            {
                 panel.removeClass('sx-panel-full');
            } else
            {
                panel.addClass('sx-panel-full');
            }

            return false;
        });
JS
        );
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Form\DataTransformer;

use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\DataTransformerInterface;
use Yucca\Component\Iterator\Iterator;

/**
 * Class IteratorToArrayTransformer
 *
 * @package Yucca\Form\DataTransformer
 */
class IteratorToArrayTransformer implements DataTransformerInterface
{
    /**
     * Transforms a yucca iterator into an array.
     *
     * @param Iterator $iterator A collection of entities
     *
     * @return mixed An array of entities
     *
     * @throws TransformationFailedException
     */
    public function transform($iterator)
    {
        if (null === $iterator) {
            return array();
        }

        if (is_array($iterator)) {
            return $iterator;
        }

        if (!$iterator instanceof Iterator) {
            throw new TransformationFailedException('Expected a Yucca\Component\Iterator\Iterator object.');
        }

        return $iterator->getArray();
    }

    /**
     * Transforms choice keys into Iterator.
     *
     * @param mixed $array An array of entities
     *
     * @return An iterator of entities
     */
    public function reverseTransform($array)
    {
        if ('' === $array || null === $array) {
            $array = array();
        } else {
            $array = (array) $array;
        }

        return $array;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Mapping;

use Yucca\Component\SourceManager;

/**
 * Class Mapper
 * @package Yucca\Component\Mapping
 */
class Mapper
{

    protected $name;
    protected $configuration;
    protected $configurationIsInitialized;

    /**
     * @var \Yucca\Component\SourceManager
     */
    protected $sourceManager;

    /**
     * Constructor
     * @param string $name
     * @param array  $configuration
     */
    public function __construct($name, array $configuration = array())
    {
        $this->name = $name;
        $this->configuration = $configuration;
    }

    /**
     * @param \Yucca\Component\SourceManager $sourceManager
     */
    public function setSourceManager(SourceManager $sourceManager)
    {
        $this->sourceManager = $sourceManager;
    }

    /**
     * @param string $propertyName
     *
     * @return mixed
     */
    public function getFieldNameFromProperty($propertyName)
    {
        $field = $propertyName;
        if (isset($this->configuration['properties'][$propertyName]['field'])) {
            $field = $this->configuration['properties'][$propertyName]['field'];
        }

        return $field;
    }

    /**
     * Load datas for specified propertyName and identifier
     *
     * @param array  $identifier
     * @param string $propertyName
     * @param null   $shardingKey
     *
     * @return array
     * @throws \Exception
     */
    public function load(array $identifier, $propertyName, $shardingKey = null)
    {
        if (isset($identifier[$propertyName])) {
            return array($propertyName=>$identifier[$propertyName]);
        }

        $field = $this->getFieldNameFromProperty($propertyName);

        $sources = $this->getSourcesFromPropertyName($propertyName);

        foreach ($sources as $sourceName) {
            $source = $this->sourceManager->getSource($sourceName);
            if ($source->canHandle($field)) {
                $mappedIdentifiers = array();
                foreach ($identifier as $id => $value) {
                    $fieldName = $this->getFieldNameFromProperty($id);
                    if ($source->canHandle($fieldName)) {
                        $mappedIdentifiers[$fieldName] = $value;
                    }
                }

                $datas = $source->load($mappedIdentifiers, false, $shardingKey);

                $mappedDatas = $this->mapFieldsToProperties($datas, $sourceName);

                return $mappedDatas;
            }
        }

        throw new \Exception("No source support field $field from property $propertyName in {$this->name}");
    }

    /**
     * @return array
     */
    public function getPropertiesToRefreshAfterSave()
    {
        $toReturn = array();
        if (array_key_exists('timestampable', $this->configuration)) {
            $toReturn = array('createdAt', 'updatedAt');
        }

        return $toReturn;
    }

    /**
     * @param mixed $identifier
     * @param array $propertyValues
     * @param null  $shardingKey
     *
     * @return array
     * @throws \Exception
     */
    public function save($identifier, array $propertyValues, $shardingKey = null)
    {
        if (false == is_array($identifier)) {
            $identifier = array();
        }

        //Check timestampable
        if (array_key_exists('timestampable', $this->configuration)) {
            if (!isset($propertyValues['createdAt'])) {
                $propertyValues['createdAt'] = new \DateTime();
            }
            $propertyValues['updatedAt'] = new \DateTime();
        }

        //First, create datas to save for each sources
        $datasBySource = array();
        foreach ($propertyValues as $propertyName => $value) {
            //Get sources for the current property
            $sources = $this->getSourcesFromPropertyName($propertyName);

            //Get field name of this property
            $field = $this->getFieldNameFromProperty($propertyName);

            //Foreach sources, if it can handle the field, save to it (should only appear once)
            $isFirstSource = true;
            foreach ($sources as $sourceName) {
                $source = $this->sourceManager->getSource($sourceName);
                if ($source->canHandle($field) && (false === is_null($value) || false === $source->isIdentifier($field) || false === $isFirstSource)) {
                    $datasBySource[$sourceName][$field] = $value;
                }
                $isFirstSource = false;
            }
        }

        //Now, map identifiers to fields
        $mappedIdentifiers = array();
        foreach ($identifier as $propertyName => $value) {
            //Get sources for the current identifier
            $sources = $this->getSourcesFromPropertyName($propertyName);

            //Get field name of this identifier
            $field = $this->getFieldNameFromProperty($propertyName);

            //Foreach sources, if it can handle the field, save to it (should only appear once)
            foreach ($sources as $sourceName) {
                $source = $this->sourceManager->getSource($sourceName);
                if ($source->canHandle($field)) {
                    $mappedIdentifiers[$sourceName][$field] = $value;
                }
            }
        }

        //order $datasBySource by reverse order of default sources
        $orderedDatasBySource = array();
        $sourceNames = $this->configuration['sources'];
        foreach ($sourceNames as $sourceName) {
            if (isset($datasBySource[$sourceName])) {
                $orderedDatasBySource[$sourceName] = $datasBySource[$sourceName];
            }
        }

        //loop on datas to save them to each sources
        $createdFieldValues = array();
        $mappedNewFields = array();
        foreach ($orderedDatasBySource as $sourceName => $datas) {
            $source = $this->sourceManager->getSource($sourceName);

            $newIdentifiers = array();
            foreach ($createdFieldValues as $field => $value) {
                if ($source->canHandle($field)) {
                    $newIdentifiers[$field] = $value;
                }
            }
            $justCreatedFieldValues = $source->save(array_merge($datas, $newIdentifiers), isset($mappedIdentifiers[$sourceName]) ? $mappedIdentifiers[$sourceName] : array(), $shardingKey);
            $createdFieldValues = array_merge($createdFieldValues, $justCreatedFieldValues);
            $mappedNewFields = array_merge($this->mapFieldsToProperties($justCreatedFieldValues, $sourceName), $mappedNewFields);
        }

        return $mappedNewFields;
    }

    /**
     * @param mixed $identifier
     * @param mixed $shardingKey
     *
     * @return $this
     */
    public function remove($identifier, $shardingKey)
    {
        $sources = array();
        foreach ($this->configuration['properties'] as $properties) {
            if (isset($properties['sources'])) {
                foreach ($properties['sources'] as $sourceName) {
                    if (false === isset($sources[$sourceName])) {
                        $sources[$sourceName] = $this->sourceManager->getSource($sourceName);
                    }
                }
            }
        }
        foreach ($this->configuration['sources'] as $sourceName) {
            if (false === isset($sources[$sourceName])) {
                $sources[$sourceName] = $this->sourceManager->getSource($sourceName);
            }
        }

        $mappedIdentifiers = array();
        foreach ($identifier as $id => $value) {
            $mappedIdentifiers[$this->getFieldNameFromProperty($id)] = $value;
        }

        foreach ($sources as $source) {
            $source->remove($mappedIdentifiers, $shardingKey);
        }

        return $this;
    }

    protected function getSourcesFromPropertyName($propertyName)
    {
        if (isset($this->configuration['properties'][$propertyName]['sources'])) {
            $sources = $this->configuration['properties'][$propertyName]['sources'];
        } else {
            if (isset($this->configuration['sources'])) {
                $sources = $this->configuration['sources'];
            } else {
                throw new \Exception("No sources defined for mapper {$this->name}");
            }
        }

        return $sources;
    }

    /**
     * @param $field
     * @param $sourceName
     * @return int|string
     */
    protected function getPropertyNameFromField($field, $sourceName)
    {
        foreach ($this->configuration['properties'] as $propertyName => $properties) {
            if (isset($properties['field']) && $field == $properties['field']) {
                if (isset($properties['sources'])) {
                    if (in_array($sourceName, $properties['sources'])) {
                        return $propertyName;
                    }
                } else {
                    if (in_array($sourceName, $this->configuration['sources'])) {
                        return $propertyName;
                    }
                }
            }
        }

        return $field;
    }

    protected function mapFieldsToProperties($datas, $sourceName)
    {
        $mappedDatas = array();
        foreach ($datas as $dataKey => $dataValue) {
            $mappedDatas[$this->getPropertyNameFromField($dataKey, $sourceName)] = $dataValue;
        }

        return $mappedDatas;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component;

use Yucca\Component\ShardingStrategy\ShardingStrategyInterface;

/**
 * Class SchemaManager
 * @package Yucca\Component
 */
class SchemaManager
{
    protected $schemaConfig;
    protected $shardingStrategies;
    /**
     * @var \Yucca\Component\ConnectionManager
     */
    protected $connectionManager;

    /**
     * @param array $schemaConfig
     */
    public function __construct(array $schemaConfig)
    {
        $this->schemaConfig = $schemaConfig;
    }

    /**
     * @param ConnectionManager $connectionManager
     */
    public function setConnectionManager(ConnectionManager $connectionManager)
    {
        $this->connectionManager = $connectionManager;
    }

    /**
     * @param string                    $shardingStrategyName
     * @param ShardingStrategyInterface $shardingStrategy
     */
    public function addShardingStrategy($shardingStrategyName, ShardingStrategyInterface $shardingStrategy)
    {
        $this->shardingStrategies[$shardingStrategyName] = $shardingStrategy;
    }

    /**
     * @param string $table
     * @param string $shardingKey
     * @param bool   $forceFromMaster
     * @return mixed
     * @throws \Exception
     */
    public function getConnectionName($table, $shardingKey, $forceFromMaster = true)
    {
        $shardingIdentifier = $this->getShardingIdentifier($table, $shardingKey);

        if (is_null($shardingIdentifier)) {
            if (1 === count($this->schemaConfig[$table]['shards'])) {
                return current($this->schemaConfig[$table]['shards']);
            } else {
                $shardsCount = count($this->schemaConfig[$table]['shards']);
                throw new \Exception("Table $table is not configured as sharded. $shardsCount connections found for table $table and sharding key $shardingKey");
            }
        }

        if (false === isset($this->schemaConfig[$table]['shards'][$shardingIdentifier])) {
            throw new \Exception("No connections found for table $table and shard $shardingIdentifier");
        }

        //Return connection name
        return $this->schemaConfig[$table]['shards'][$shardingIdentifier];
    }

    /**
     * @param string $table
     * @param mixed  $shardingKey
     *
     * @return null
     * @throws \Exception
     */
    public function getShardingIdentifier($table, $shardingKey = null)
    {
        if (is_null($shardingKey)) {
            return null;
        }

        //Look for table config
        if (false === isset($this->schemaConfig[$table])) {
            throw new \InvalidArgumentException("$table is not in given configuration");
        }

        //Look for sharding strategy
        if (false === isset($this->schemaConfig[$table]['sharding_strategy'])) {
            return null;
        }
        $shardingStrategy = $this->schemaConfig[$table]['sharding_strategy'];
        if (false === isset($this->shardingStrategies[$shardingStrategy])) {
            throw new \Exception("Sharding strategy $shardingStrategy not found for table $table");
        }

        //Look for sharding identifier
        return $this->shardingStrategies[$shardingStrategy]->getShardingIdentifier(
            $this->schemaConfig[$table],
            $shardingKey
        );
    }

    /**
     * @param string $tableName
     * @param array  $criterias
     * @param null   $shardingKey
     *
     * @return int
     * @throws \Doctrine\DBAL\DBALException
     * @throws \Exception
     */
    public function remove($tableName, array $criterias, $shardingKey = null)
    {
        $connection = $this->connectionManager->getConnection(
            $this->getConnectionName($tableName, $shardingKey, true),
            true
        );

        $shardingIdentifier = $this->getShardingIdentifier($tableName, $shardingKey);
        if (isset($shardingIdentifier)) {
            $tableName = sprintf('%1$s_%2$s', $tableName, $shardingIdentifier);
        }

        $deleteCriteria = $deleteCriteriaValues = array();
        foreach ($criterias as $columnName => $criteria) {
            if (is_array($criteria) && false == empty($criteria)) {
                $deleteCriteria[] = $columnName.' IN ('.implode(',', array_fill(0, count($criteria), '?')).')';
                $deleteCriteriaValues = array_merge($deleteCriteriaValues, $criteria);
            } else {
                $deleteCriteria[] = $columnName.' = ?';
                if ($criteria instanceof \Yucca\Model\ModelInterface) {
                    $deleteCriteriaValues[] = $criteria->getId();
                } else {
                    $deleteCriteriaValues[] = $criteria;
                }
            }
        }
        $query = 'DELETE FROM '.$tableName.' WHERE '.implode(' AND ', $deleteCriteria);

        $connection->executeUpdate($query, $deleteCriteriaValues);

        return $this;
    }

    /**
     * Fetch one entry from database
     *
     * @param string $tableName
     * @param array  $identifier
     * @param null   $shardingKey
     * @param bool   $forceFromMaster
     *
     * @return mixed
     * @throws \Exception
     */
    public function fetchOne($tableName, array $identifier, $shardingKey = null, $forceFromMaster = true)
    {
        return $this->fetch($tableName, $identifier, $shardingKey, array('*'), false, $forceFromMaster);
    }

    /**
     * @param string $tableName
     * @param array  $criterias
     * @param array  $identifiersFields
     * @param null   $shardingKey
     * @param bool   $forceFromMaster
     * @param array  $options
     *
     * @return array
     * @throws \Exception
     */
    public function fetchIds($tableName, array $criterias, array $identifiersFields = array('id'), $shardingKey = null, $forceFromMaster = false, array $options = array())
    {
        return $this->fetch($tableName, $criterias, $shardingKey, $identifiersFields, true, $forceFromMaster, $options);
    }

    protected function fetch($tableName, array $criterias, $shardingKey, array $fields, $allowEmptyCriterias, $forceFromMaster, array $options = array())
    {

        if ((false === $allowEmptyCriterias) && empty($criterias)) {
            if (is_array($tableName)) {
                $tableName = implode(',', array_keys($tableName));
            }
            throw new \Exception("Trying to load from $tableName with no identifiers");
        }
        if (empty($tableName)) {
            throw new \RuntimeException('table name must not be empty');
        }

        if (is_array($tableName)) {
            $connectionName = $this->getConnectionName(key($tableName), $shardingKey, $forceFromMaster);
            $from = array();
            $join = array();

            foreach ($tableName as $table => $tableJoin) {
                if (isset($tableJoin['table'])) {
                    $table = $tableJoin['table'];
                }
                if ($connectionName != $this->getConnectionName($table, $shardingKey, $forceFromMaster)) {
                    throw new \RuntimeException('Expected connection : '.$connectionName.', but '.$table.' use another one');
                }

                $shardingIdentifier = $this->getShardingIdentifier($table, $shardingKey);
                if (isset($shardingIdentifier)) {
                    $table = sprintf('%1$s_%2$s', $table, $shardingIdentifier);
                }

                if (empty($tableJoin['join'])) {
                    $from[] = $table.(isset($tableJoin['alias'])?' AS '.$tableJoin['alias']:'');
                } else {
                    $join[] = sprintf($tableJoin['join'], $table);
                }
            }

            $tables = implode(',', $from).' '.implode(' ', $join);
            $connection = $this->connectionManager->getConnection(
                $connectionName,
                $forceFromMaster
            );
        } else {
            $connection = $this->connectionManager->getConnection(
                $this->getConnectionName($tableName, $shardingKey, $forceFromMaster),
                $forceFromMaster
            );

            $shardingIdentifier = $this->getShardingIdentifier($tableName, $shardingKey);
            if (isset($shardingIdentifier)) {
                $tableName = sprintf('%1$s_%2$s', $tableName, $shardingIdentifier);
            }

            $tables = '`'.$tableName.'`';
        }

        $fields = implode(',', $fields);

        $sql = "SELECT $fields FROM $tables";
        $params = array();
        $whereCriterias = array();
        foreach ($criterias as $criteriaKey => $criteriaValue) {
            if (is_array($criteriaValue) && 1 == count($criteriaValue)) {
                $criteriaValue = current($criteriaValue);
            }
            if (is_array($criteriaValue)) {
                $parametersNames = array();
                $i = 0;
                $addIsNull=false;
                $orCriteria = array();
                foreach ($criteriaValue as $v) {
                    if ($v instanceof \Yucca\Model\ModelInterface) {
                        $params[":".str_replace('.', '_', $criteriaKey)."$i"] = $v->getId();
                        $parametersNames[] = ":".str_replace('.', '_', $criteriaKey)."$i";
                        $i++;
                    } elseif (is_null($v)) {
                        $addIsNull = true;
                    } elseif (is_scalar($v)) {
                        $params[":".str_replace('.', '_', $criteriaKey)."$i"] = $v;
                        $parametersNames[] = ":".str_replace('.', '_', $criteriaKey)."$i";
                        $i++;
                    } elseif ($v instanceof \Yucca\Component\Selector\Expression) {
                        $expression = $v->toString('database');
                        if (false === empty($expression)) {
                            $orCriteria[] = $expression;
                            foreach ($v->getParams() as $paramName => $paramValue) {
                                $params[":".$paramName] = $paramValue;
                            }
                        }
                    } else {
                        throw new \Exception("Don't know what to do with criteria $criteriaKey");
                    }
                }
                if ($orCriteria) {
                    $whereCriterias[] = '('.implode(' OR ', $orCriteria).')';
                } else {
                    $in = "`".str_replace('.', '`.`', $criteriaKey)."` IN (".implode(',', $parametersNames).")";
                    if ($addIsNull) {
                        $whereCriterias[] = "(`".str_replace('.', '`.`', $criteriaKey)."` IS NULL OR $in)";
                    } else {
                        $whereCriterias[] = $in;
                    }
                }
            } else {
                if ($criteriaValue instanceof \Yucca\Model\ModelInterface) {
                    $whereCriterias[] = "`".str_replace('.', '`.`', $criteriaKey)."`=:".str_replace('.', '_', $criteriaKey)."";
                    $params[":".str_replace('.', '_', $criteriaKey)] = $criteriaValue->getId();
                } elseif (is_null($criteriaValue)) {
                    $whereCriterias[] = "`".str_replace('.', '`.`', $criteriaKey)."` IS NULL";
                } elseif (is_scalar($criteriaValue)) {
                    $whereCriterias[] = "`".str_replace('.', '`.`', $criteriaKey)."`=:".str_replace('.', '_', $criteriaKey)."";
                    $params[":".str_replace('.', '_', $criteriaKey)] = $criteriaValue;
                } elseif ($criteriaValue instanceof \Yucca\Component\Selector\Expression) {
                    $expression = $criteriaValue->toString('database');
                    if (false === empty($expression)) {
                        $whereCriterias[] = $expression;
                        foreach ($criteriaValue->getParams() as $paramName => $paramValue) {
                            $params[":".$paramName] = $paramValue;
                        }
                    }
                } else {
                    throw new \Exception("Don't know what to do with criteria $criteriaKey");
                }
            }
        }
        if (false === empty($whereCriterias)) {
            $sql .= ' WHERE '.implode(" AND ", $whereCriterias);
        }

        if (isset($options['groupBy'])) {
            $sql .= ' GROUP BY '.$options['groupBy'];
        }
        if (isset($options['orderBy'])) {
            $sql .= ' ORDER BY '.$options['orderBy'];
        }

        if (isset($options['limit'])) {
            $sql .= ' LIMIT '.$options['limit'];
        }

        return $connection->fetchAll($sql, $params);
    }
}


<?php

namespace Uvinum\ZipCode;

final class Validator
{
    /** @var array */
    private $zip_code_patterns_by_country = [
        'AC' => 'ASCN 1ZZ',
        'AD' => 'AD[1-7]0\\d',
        'AF' => '\\d{4}',
        'AI' => '(?:AI-)?2640',
        'AL' => '\\d{4}',
        'AM' => '(?:37)?\\d{4}',
        'AR' => '((?:[A-HJ-NP-Z])?\\d{4})([A-Z]{3})?',
        'AS' => '(96799)(?:[ \\-](\\d{4}))?',
        'AT' => '\\d{4}',
        'AU' => '\\d{4}',
        'AX' => '22\\d{3}',
        'AZ' => '\\d{4}',
        'BA' => '\\d{5}',
        'BB' => 'BB\\d{5}',
        'BD' => '\\d{4}',
        'BE' => '\\d{4}',
        'BG' => '\\d{4}',
        'BH' => '(?:\\d|1[0-2])\\d{2}',
        'BL' => '9[78][01]\\d{2}',
        'BM' => '[A-Z]{2} ?[A-Z0-9]{2}',
        'BN' => '[A-Z]{2} ?\\d{4}',
        'BR' => '\\d{5}-?\\d{3}',
        'BT' => '\\d{5}',
        'BY' => '\\d{6}',
        'CA' => '[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d',
        'CC' => '6799',
        'CH' => '\\d{4}',
        'CL' => '\\d{7}',
        'CN' => '\\d{6}',
        'CO' => '\\d{6}',
        'CR' => '\\d{4,5}|\\d{3}-\\d{4}',
        'CV' => '\\d{4}',
        'CX' => '6798',
        'CY' => '\\d{4}',
        'CZ' => '\\d{3} ?\\d{2}',
        'DE' => '\\d{5}',
        'DK' => '\\d{4}',
        'DO' => '\\d{5}',
        'DZ' => '\\d{5}',
        'EC' => '\\d{6}',
        'EE' => '\\d{5}',
        'EG' => '\\d{5}',
        'EH' => '\\d{5}',
        'ES' => '\\d{5}',
        'ET' => '\\d{4}',
        'FI' => '\\d{5}',
        'FK' => 'FIQQ 1ZZ',
        'FM' => '(9694[1-4])(?:[ \\-](\\d{4}))?',
        'FO' => '\\d{3}',
        'FR' => '\\d{2} ?\\d{3}',
        'GB' => 'GIR ?0AA|(?:(?:AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}))|BFPO ?\\d{1,4}',
        'GE' => '\\d{4}',
        'GF' => '9[78]3\\d{2}',
        'GG' => 'GY\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
        'GI' => 'GX11 1AA',
        'GL' => '39\\d{2}',
        'GN' => '\\d{3}',
        'GP' => '9[78][01]\\d{2}',
        'GR' => '\\d{3} ?\\d{2}',
        'GS' => 'SIQQ 1ZZ',
        'GT' => '\\d{5}',
        'GU' => '(969(?:[12]\\d|3[12]))(?:[ \\-](\\d{4}))?',
        'GW' => '\\d{4}',
        'HM' => '\\d{4}',
        'HN' => '\\d{5}',
        'HR' => '\\d{5}',
        'HT' => '\\d{4}',
        'HU' => '\\d{4}',
        'ID' => '\\d{5}',
        'IE' => '[\\dA-Z]{3} ?[\\dA-Z]{4}',
        'IL' => '\\d{5}(?:\\d{2})?',
        'IM' => 'IM\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
        'IN' => '\\d{6}',
        'IO' => 'BBND 1ZZ',
        'IQ' => '\\d{5}',
        'IR' => '\\d{5}-?\\d{5}',
        'IS' => '\\d{3}',
        'IT' => '\\d{5}',
        'JE' => 'JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
        'JO' => '\\d{5}',
        'JP' => '\\d{3}-?\\d{4}',
        'KE' => '\\d{5}',
        'KG' => '\\d{6}',
        'KH' => '\\d{5}',
        'KR' => '\\d{3}(?:\\d{2}|-\\d{3})',
        'KW' => '\\d{5}',
        'KY' => 'KY\\d-\\d{4}',
        'KZ' => '\\d{6}',
        'LA' => '\\d{5}',
        'LB' => '(?:\\d{4})(?: ?(?:\\d{4}))?',
        'LI' => '948[5-9]|949[0-7]',
        'LK' => '\\d{5}',
        'LR' => '\\d{4}',
        'LS' => '\\d{3}',
        'LT' => '\\d{5}',
        'LU' => '\\d{4}',
        'LV' => 'LV-\\d{4}',
        'MA' => '\\d{5}',
        'MC' => '980\\d{2}',
        'MD' => '\\d{4}',
        'ME' => '8\\d{4}',
        'MF' => '9[78][01]\\d{2}',
        'MG' => '\\d{3}',
        'MH' => '(969[67]\\d)(?:[ \\-](\\d{4}))?',
        'MK' => '\\d{4}',
        'MM' => '\\d{5}',
        'MN' => '\\d{5}',
        'MP' => '(9695[012])(?:[ \\-](\\d{4}))?',
        'MQ' => '9[78]2\\d{2}',
        'MT' => '[A-Z]{3} ?\\d{2,4}',
        'MU' => '\\d{3}(?:\\d{2}|[A-Z]{2}\\d{3})',
        'MV' => '\\d{5}',
        'MX' => '\\d{5}',
        'MY' => '\\d{5}',
        'MZ' => '\\d{4}',
        'NC' => '988\\d{2}',
        'NE' => '\\d{4}',
        'NF' => '2899',
        'NG' => '\\d{6}',
        'NI' => '\\d{5}',
        'NL' => '[1-9]{1}\\d{3}\s?([A-Z]{2})?',
        'NO' => '\\d{4}',
        'NP' => '\\d{5}',
        'NZ' => '\\d{4}',
        'OM' => '(?:PC )?\\d{3}',
        'PE' => '(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}',
        'PF' => '987\\d{2}',
        'PG' => '\\d{3}',
        'PH' => '\\d{4}',
        'PK' => '\\d{5}',
        'PL' => '\\d{2}-\\d{3}',
        'PM' => '9[78]5\\d{2}',
        'PN' => 'PCRN 1ZZ',
        'PR' => '(00[679]\\d{2})(?:[ \\-](\\d{4}))?',
        'PT' => '\\d{4}-\\d{3}',
        'PW' => '(969(?:39|40))(?:[ \\-](\\d{4}))?',
        'PY' => '\\d{4}',
        'RE' => '9[78]4\\d{2}',
        'RO' => '\\d{6}',
        'RS' => '\\d{5,6}',
        'RU' => '\\d{6}',
        'SA' => '\\d{5}',
        'SE' => '\\d{3} ?\\d{2}',
        'SG' => '\\d{6}',
        'SH' => '(?:ASCN|STHL) 1ZZ',
        'SI' => '\\d{4}',
        'SJ' => '\\d{4}',
        'SK' => '\\d{3} ?\\d{2}',
        'SM' => '4789\\d',
        'SN' => '\\d{5}',
        'SO' => '[A-Z]{2} ?\\d{5}',
        'SV' => 'CP [1-3][1-7][0-2]\\d',
        'SZ' => '[HLMS]\\d{3}',
        'TA' => 'TDCU 1ZZ',
        'TC' => 'TKCA 1ZZ',
        'TH' => '\\d{5}',
        'TJ' => '\\d{6}',
        'TM' => '\\d{6}',
        'TN' => '\\d{4}',
        'TR' => '\\d{5}',
        'TW' => '\\d{3}(?:\\d{2})?',
        'TZ' => '\\d{4,5}',
        'UA' => '\\d{5}',
        'UM' => '96898',
        'US' => '(\\d{5})(?:[ \\-](\\d{4}))?',
        'UY' => '\\d{5}',
        'UZ' => '\\d{6}',
        'VA' => '00120',
        'VC' => 'VC\\d{4}',
        'VE' => '\\d{4}',
        'VG' => 'VG\\d{4}',
        'VI' => '(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?',
        'VN' => '\\d{6}',
        'WF' => '986\\d{2}',
        'XK' => '[1-7]\\d{4}',
        'YT' => '976\\d{2}',
        'ZA' => '\\d{4}',
        'ZM' => '\\d{5}',
    ];

    /**
     * @param string $a_country_iso_code
     * @param string $a_zip_code
     *
     * @return bool
     */
    public function validate($a_country_iso_code, $a_zip_code)
    {
        if ($this->notHavePatternToValidateZipCodeForThisCountry($a_country_iso_code))
        {
            throw new \InvalidArgumentException(
                'Not have pattern to validate "' . $a_country_iso_code . '" zip codes, or "' . $a_country_iso_code . '" is not a valid ISO 3166-1 CODE '
            );
        }
        $pattern = $this->getPatternToValidateZipCodeForThisCountry($a_country_iso_code);

        return (bool) preg_match("/^{$pattern}$/i", $a_zip_code);
    }

    /**
     * @param string $a_country_iso_code
     *
     * @return bool
     */
    private function notHavePatternToValidateZipCodeForThisCountry($a_country_iso_code)
    {
        return !array_key_exists($a_country_iso_code, $this->zip_code_patterns_by_country);
    }

    /**
     * @param string $country_iso_code
     *
     * @return array
     */
    private function getPatternToValidateZipCodeForThisCountry($country_iso_code)
    {
        return $this->zip_code_patterns_by_country[$country_iso_code];
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Iterator;

use Yucca\Component\Selector\SelectorInterface;
use Yucca\Component\EntityManager;

/**
 * Class Iterator
 * @package Yucca\Component\Iterator
 */
class Iterator implements \Countable, \Iterator
{
    /**
     * @var \Yucca\Model\ModelInterface
     */
    protected $model = null;

    /**
     * @var \Yucca\Component\Selector\SelectorInterface
     */
    protected $selector;

    /**
     * @var \Yucca\Component\EntityManager
     */
    protected $entityManager;

    /**
     * Tell whether we return new objects or use the same one
     * If false, we keep only one model in memory to loop over, to help keeping memory low
     * @var boolean
     */
    protected $wantNewModel = false;

    /**
     * @var string
     */
    protected $modelClassName;

    /**
     * Constructor
     * @param \Yucca\Component\Selector\SelectorInterface $selector
     * @param \Yucca\Component\EntityManager              $entityManager
     * @param string                                      $modelClassName
     */
    public function __construct(SelectorInterface $selector, EntityManager $entityManager, $modelClassName)
    {
        $this->entityManager = $entityManager;
        $this->selector = $selector;
        $this->modelClassName = $modelClassName;
    }

    /**
     * Set whether we want new object or a single one to iterate over
     * @param boolean $value
     *
     * @return \Yucca\Component\Iterator\Iterator
     */
    public function wantNewModel($value = true)
    {
        $this->wantNewModel = ($value ? true : false);

        return $this;
    }

    /**
     * give model count
     * @return int
     */
    public function count()
    {
        return $this->selector->count();
    }

    /**
     * retrieve the current Model
     * @return \Yucca\Model\ModelInterface
     */
    public function current()
    {
        if (true === $this->wantNewModel) {
            return $this->entityManager->load($this->modelClassName, $this->selector->current());
        } else {
            $this->initializeModel($this->selector->current(), $this->selector->currentShardingKey());

            $this->entityManager->resetModel($this->model, $this->selector->current());

            return $this->model;
        }
    }

    /**
     * retrieve the current key
     * @return mixed
     */
    public function key()
    {
        return $this->selector->key();
    }

    /**
     * Next
     */
    public function next()
    {
        $this->selector->next();
    }

    /**
     * First
     * @return \Yucca\Component\Iterator\Iterator
     */
    public function rewind()
    {
        $this->selector->rewind();

        return $this;
    }

    /**
     * Is there any model to fetch ?
     * @return boolean
     */
    public function valid()
    {
        return $this->selector->valid();
    }

    /**
     * @return \Yucca\Component\Selector\SelectorInterface
     */
    public function getSelector()
    {
        return $this->selector;
    }

    /**
     * @return array
     */
    public function getArray()
    {
        $toReturn = array();

        $oldWantNewModel = $this->wantNewModel;
        $this->wantNewModel(true);

        foreach ($this as $object) {
            $toReturn[] = $object;
        }
        $this->wantNewModel($oldWantNewModel);

        return $toReturn;
    }

    /**
     * initialize the unique model
     * @param mixed $id
     * @param mixed $shardingKey
     *
     * @return self
     */
    protected function initializeModel($id, $shardingKey = null)
    {
        if (is_null($this->model)) {
            $this->model = $this->entityManager->load($this->modelClassName, $id, $shardingKey);
        }

        return $this;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\ShardingStrategy;

/**
 * Class Modulo
 * @package Yucca\Component\ShardingStrategy
 */
class Modulo implements ShardingStrategyInterface
{
    /**
     * @param array $tableConfig
     * @param mixed $shardingKey
     *
     * @return int|null
     * @throws \Exception
     */
    public function getShardingIdentifier(array $tableConfig, $shardingKey)
    {
        is_array($tableConfig['shards']) or $tableConfig['shards'] = array();

        $count = count($tableConfig['shards']);

        $toReturn = null;

        switch ($count) {
            case 0:
                throw new \Exception("No shards defined : can't compute modulo");
                break;
            case 1:
                return null;
                break;

            default:
                $toReturn = $shardingKey % count($tableConfig['shards']);
                break;
        }

        return $toReturn;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Source;

use Yucca\Component\ConnectionManager;
use Yucca\Component\SchemaManager;
use Yucca\Component\Source\Exception\BreakSaveChainException;
use Yucca\Component\Source\Exception\NoDataException;
use Yucca\Component\Source\DataParser\DataParser;

/**
 * Class DatabaseSingleRow
 * @package Yucca\Component\Source
 */
class DatabaseSingleRow extends SourceAbstract
{

    protected $tableName;

    /**
     * @var \Yucca\Component\SchemaManager
     */
    protected $schemaManager;

    /**
     * @var \Yucca\Component\ConnectionManager
     */
    protected $connectionManager;

    /**
     * @var \Yucca\Component\Source\DataParser\DataParser
     */
    protected $dataParser;

    /**
     * Constructor
     * @param string $sourceName
     * @param array  $configuration
     * @throws \InvalidArgumentException
     */
    public function __construct($sourceName, array $configuration = array())
    {
        parent::__construct($sourceName, $configuration);

        if (false === isset($this->configuration['table_name'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'table_name' key");
        }
        $this->tableName = $this->configuration['table_name'];
        $this->breakChainOnSave = isset($this->configuration['break_chain_on_save']) ? $this->configuration['break_chain_on_save'] : null;
    }

    /**
     * @param \Yucca\Component\SchemaManager $schemaManager
     * @return \Yucca\Component\Source\DatabaseSingleRow
     */
    public function setSchemaManager(SchemaManager $schemaManager)
    {
        $this->schemaManager = $schemaManager;

        return $this;
    }

    /**
     * @param \Yucca\Component\ConnectionManager $connectionManager
     * @return \Yucca\Component\Source\DatabaseSingleRow
     */
    public function setConnectionManager(ConnectionManager $connectionManager)
    {
        $this->connectionManager = $connectionManager;

        return $this;
    }

    /**
     * @param DataParser $dataParser
     * @return DatabaseSingleRow
     */
    public function setDataParser(DataParser $dataParser)
    {
        $this->dataParser = $dataParser;

        return $this;
    }

    /**
     * @param array $identifier
     * @param bool  $rawData
     * @param mixed $shardingKey
     *
     * @return array|mixed
     * @throws NoDataException
     * @throws \Exception
     */
    public function load(array $identifier, $rawData, $shardingKey)
    {
        $datas = $this->schemaManager->fetchOne($this->tableName, $identifier, $shardingKey);
        if (empty($datas) || 1 != count($datas)) {
            if (count($datas)) {
                throw new NoDataException("Too much datas for $this->tableName with ids : ".var_export($identifier, true));
            } else {
                throw new NoDataException("No datas for $this->tableName with ids : ".var_export($identifier, true));
            }
        }

        if ($rawData) {
            return current($datas);
        } else {
            return $this->dataParser->decode(current($datas), $this->configuration['fields']);
        }
    }

    /**
     * @param array $identifier
     * @param null  $shardingKey
     *
     * @return $this
     */
    public function remove(array $identifier, $shardingKey = null)
    {
        $this->schemaManager->remove($this->tableName, $identifier, $shardingKey);

        return $this;
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     * @param bool  $replace
     * @param bool  $rawData
     *
     * @return array
     * @throws BreakSaveChainException
     * @throws \Doctrine\DBAL\DBALException
     * @throws \Exception
     */
    public function save($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null, $replace = false, $rawData = false)
    {
        if ($this->breakChainOnSave) {
            throw new BreakSaveChainException('Source configured to break chain on save');
        }

        //Extract sharding key from identifier
        $originalIdentifier = $identifier;

        //Check if we have to insert or update
        $insert =empty($identifier);

        //find identifiers
        $identifierFieldName = null;
        foreach ($this->configuration['fields'] as $field => $fieldProperties) {
            if (isset($fieldProperties['type']) && 'identifier' == $fieldProperties['type'] && false === array_key_exists($field, $datas)) {
                $identifierFieldName = $field;
                if (array_key_exists($field, $datas) && is_null($datas[$field])) {
                    unset($datas[$field]);
                }
            }
        }

        //Check if we have datas to update or insert
        if (empty($datas)) {
            throw new \Exception("Trying to save empty datas for table {$this->tableName} and identifier ".var_export($identifier, true));
        }

        //Get Connection and table name
        $connection = $this->connectionManager->getConnection(
            $this->schemaManager->getConnectionName($this->tableName, $shardingKey, true),
            true
        );
        $shardingIdentifier = $this->schemaManager->getShardingIdentifier($this->tableName, $shardingKey);
        $tableName = $this->tableName;
        if (isset($shardingIdentifier)) {
            $tableName = sprintf('%1$s_%2$s', $this->tableName, $shardingIdentifier);
        }

        //Parse datas
        if (false === $rawData) {
            $datas = $this->dataParser->encode($datas, $this->configuration['fields']);
        }
        //check if it's an insert or update
        if ($insert) {
            //Insert
            $affectedRows = $connection->insert($tableName, $datas);

            if (isset($identifierFieldName)) {
                return array(
                    'sharding_key' => $shardingKey,
                    $identifierFieldName=>$connection->lastInsertId(),
                );
            } else {
                return $identifier;
            }
        } else {
            if ($replace) {
                //Replace
                $connection->connect();
                $set = array();
                $params = array();
                foreach ($datas as $columnName => $value) {
                    $set[$columnName] = $columnName.' = ?';
                    $params[$columnName] = $value;
                }
                foreach ($identifier as $columnName => $value) {
                    $set[$columnName] = $columnName.' = ?';
                    $params[$columnName] = $value;
                }

                $sql  = 'REPLACE INTO `'.$tableName.'` SET '.implode(', ', $set);

                $affectedRows = $connection->executeUpdate($sql, array_values($params));

                return $originalIdentifier;
            } else {
                //Update
                $affectedRows = $connection->update('`'.$tableName.'`', $datas, $identifier);

                return $originalIdentifier;
            }
        }
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     *
     * @return array
     * @throws BreakSaveChainException
     * @throws \Exception
     */
    public function saveAfterLoading($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null)
    {
        return $this->save($datas, $identifier, $shardingKey, $affectedRows, true, true);
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Selector;

/**
 * Class Ids
 * @package Yucca\Component\Selector
 */
class Ids implements SelectorInterface
{
    /**
     * @var array
     */
    protected $ids;
    protected $preparedIds;
    protected $isQueryPrepared = false;

    protected $keyName;
    protected $limit = null;

    /**
     * @param array  $ids
     * @param string $keyName
     */
    public function __construct(array $ids, $keyName = null)
    {
        $this->ids = $ids;
        $this->keyName = $keyName ;
    }

    /**
     * @return mixed
     */
    public function current()
    {
        $this->prepareQuery();

        if (isset($this->keyName)) {
            return array($this->keyName => current($this->preparedIds));
        } else {
            return current($this->preparedIds);
        }
    }

    /**
     *
     */
    public function next()
    {
        $this->prepareQuery();

        next($this->preparedIds);
    }

    /**
     * @return mixed
     */
    public function key()
    {
        $this->prepareQuery();

        return key($this->preparedIds);
    }

    /**
     * @return bool
     */
    public function valid()
    {
        $this->prepareQuery();

        return ( false !== current($this->preparedIds) );
    }

    /**
     *
     */
    public function rewind()
    {
        $this->prepareQuery();

        reset($this->preparedIds);
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->ids);
    }

    /**
     * @return null
     */
    public function currentShardingKey()
    {
        return null;
    }

    /**
     * @param array $criteria
     *
     * @throws \RuntimeException
     */
    public function setCriteria(array $criteria)
    {
        throw new \RuntimeException();
    }

    /**
     * @param mixed $value
     *
     * @return $this
     */
    public function limit($value)
    {
        if (0 === preg_match('/^([0-9]+)(,[0-9]+)?$/', $value)) {
            throw new \InvalidArgumentException($value.' doesn\'t match limit pattern');
        }
        $this->limit = $value;

        return $this;
    }

    /**
     *
     */
    protected function prepareQuery()
    {
        if (false === $this->isQueryPrepared) {
            if ($this->limit) {
                $limits = explode(',', $this->limit);
                if (count($limits) == 1) {
                    $this->preparedIds = array_slice($this->ids, 0, $limits[0]);
                } else {
                    $this->preparedIds = array_slice($this->ids, $limits[0], $limits[1]);
                }
            } else {
                $this->preparedIds = $this->ids;
            }
            $this->isQueryPrepared = true;
        }
    }
}


<?php
namespace Yucca\Form\ChoiceList;

use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Yucca\Component\EntityManager;
use Yucca\Component\Iterator\Iterator;

/**
 * Class ChoiceLoader
 * @package Yucca\Form\ChoiceList
 */
class ChoiceLoader implements ChoiceLoaderInterface
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * Contains the query builder that builds the query for fetching the
     * entities
     *
     * This property should only be accessed through queryBuilder.
     *
     * @var Iterator
     */
    private $iterator;

    /**
     * The preferred entities.
     *
     * @var array
     */
    private $preferredEntities = array();

    private $modelClassName;
    private $selectorClassName;

    private $choiceList;

    private $factory;
    /**
     * Creates a new entity choice list.
     *
     * @param EntityManager              $manager           An EntityManager instance
     * @param string                     $modelClassName    The model class name
     * @param string                     $selectorClassName The selector class name
     * @param string                     $labelPath         The property path used for the label
     * @param Iterator                   $iterator          An optional query builder
     * @param array                      $preferredEntities An array of preferred choices
     * @param string                     $groupPath         A property path pointing to the property used
     *                                                     to group the choices. Only allowed if
     *                                                     the choices are given as flat array.
     * @param ChoiceListFactoryInterface $factory
     */
    public function __construct(EntityManager $manager, $modelClassName, $selectorClassName, $labelPath = null, $iterator = null, array $preferredEntities = array(), $groupPath = null, ChoiceListFactoryInterface $factory = null)
    {
        $this->entityManager = $manager;
        $this->modelClassName = $modelClassName;
        $this->selectorClassName = $selectorClassName;
        $this->iterator = $iterator;
        $this->preferredEntities = $preferredEntities;
        $this->factory = $factory;
    }

    /**
     * {@inheritdoc}
     */
    public function loadChoiceList($value = null)
    {
        if (is_null($value)) {
            $value = function ($choice) {
                if ($choice instanceof \Yucca\Model\ModelInterface) {
                    return $choice->getId();
                } else {
                    return (string) $choice;
                }
            };
        }

        if ($this->choiceList) {
            return $this->choiceList;
        }

        if ($this->iterator) {
            if (is_array($this->iterator)) {
                $entities = $this->iterator;
            } else {
                $entities = $this->iterator->getArray();
            }
        } else {
            $selector = $this->entityManager->getSelectorManager()->getSelector($this->selectorClassName);
            $iterator = new Iterator(
                $selector,
                $this->entityManager,
                $this->modelClassName
            );
            $entities = $iterator->getArray();
        }

        $this->choiceList = $this->factory->createListFromChoices($entities, $value);

        return $this->choiceList;
    }

    /**
     * {@inheritdoc}
     */
    public function loadValuesForChoices(array $choices, $value = null)
    {
        // Performance optimization
        if (empty($choices)) {
            return array();
        }

        // Optimize performance for single-field identifiers. We already
        // know that the IDs are used as values

        // Attention: This optimization does not check choices for existence
//        if (!$this->choiceList && $this->idReader->isSingleId()) {
//            $values = array();
//
//            // Maintain order and indices of the given objects
//            foreach ($choices as $i => $object) {
//                if ($object instanceof $this->class) {
//                    // Make sure to convert to the right format
//                    $values[$i] = (string) $this->idReader->getIdValue($object);
//                }
//            }
//
//            return $values;
//        }

        return $this->loadChoiceList($value)->getValuesForChoices($choices);
    }

    /**
     * {@inheritdoc}
     */
    public function loadChoicesForValues(array $values, $value = null)
    {
        // Performance optimization
        // Also prevents the generation of "WHERE id IN ()" queries through the
        // object loader. At least with MySQL and on the development machine
        // this was tested on, no exception was thrown for such invalid
        // statements, consequently no test fails when this code is removed.
        // https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
        if (empty($values)) {
            return array();
        }

        // Optimize performance in case we have an object loader and
        // a single-field identifier
//        if (!$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) {
//            $unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);
//            $objectsById = array();
//            $objects = array();
//
//            // Maintain order and indices from the given $values
//            // An alternative approach to the following loop is to add the
//            // "INDEX BY" clause to the Doctrine query in the loader,
//            // but I'm not sure whether that's doable in a generic fashion.
//            foreach ($unorderedObjects as $object) {
//                $objectsById[$this->idReader->getIdValue($object)] = $object;
//            }
//
//            foreach ($values as $i => $id) {
//                if (isset($objectsById[$id])) {
//                    $objects[$i] = $objectsById[$id];
//                }
//            }
//
//            return $objects;
//        }

        return $this->loadChoiceList($value)->getChoicesForValues($values);
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component\Source;

use Yucca\Component\ConnectionManager;
use Yucca\Component\SchemaManager;
use Yucca\Component\Source\DataParser\DataParser;

/**
 * @todo : handle data parser
 */
class DatabaseMultipleRow extends SourceAbstract
{

    /**
     * @var string
     */
    protected $tableName;

    /**
     * @var string
     */
    protected $nameField;

    /**
     * @var string
     */
    protected $valueField;

    /**
     * @var string
     */
    protected $mapping;

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

    /**
     * @var \Yucca\Component\SchemaManager
     */
    protected $schemaManager;

    /**
     * @var \Yucca\Component\ConnectionManager
     */
    protected $connectionManager;

    /**
     * @var \Yucca\Component\Source\DataParser\DataParser
     */
    protected $dataParser;

    /**
     * DatabaseMultipleRow constructor.
     *
     * @param string $sourceName
     * @param array  $configuration
     */
    public function __construct($sourceName, array $configuration = array())
    {
        parent::__construct($sourceName, $configuration);

        if (false === isset($this->configuration['table_name'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'table_name' key");
        }
        $this->tableName = $this->configuration['table_name'];

        if (false === isset($this->configuration['name_field'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'name_field' key");
        }
        $this->nameField = $this->configuration['name_field'];

        if (false === isset($this->configuration['value_field'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'value_field' key");
        }
        $this->valueField = $this->configuration['value_field'];

        if (false === isset($this->configuration['mapping'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'mapping' key");
        }
        $this->mapping = $this->configuration['mapping'];

        if (false === isset($this->configuration['fields'])) {
            throw new \InvalidArgumentException("Configuration array must contain a 'fields' key");
        }
        $this->fields = $this->configuration['fields'];
    }

    /**
     * @param \Yucca\Component\SchemaManager $schemaManager
     * @return \Yucca\Component\Source\DatabaseMultipleRow
     */
    public function setSchemaManager(SchemaManager $schemaManager)
    {
        $this->schemaManager = $schemaManager;

        return $this;
    }

    /**
     * @param \Yucca\Component\ConnectionManager $connectionManager
     * @return \Yucca\Component\Source\DatabaseMultipleRow
     */
    public function setConnectionManager(ConnectionManager $connectionManager)
    {
        $this->connectionManager = $connectionManager;

        return $this;
    }

    /**
     * @param \Yucca\Component\Source\DataParser\DataParser $dataParser
     * @return \Yucca\Component\Source\DatabaseMultipleRow
     */
    public function setDataParser(DataParser $dataParser)
    {
        $this->dataParser = $dataParser;

        return $this;
    }

    /**
     * @param array $identifier
     * @param bool  $rawData
     * @param mixed $shardingKey
     *
     * @return array
     */
    public function load(array $identifier, $rawData, $shardingKey)
    {
        $mappedIdentifier = $this->mapIdentifier($identifier);
        $datas = $this->schemaManager->fetchIds($this->tableName, $mappedIdentifier, array($this->nameField, $this->valueField), $shardingKey);
        $toReturn = array();
        foreach ($datas as $row) {
            $toReturn[$row[$this->nameField]] = $row[$this->valueField];
        }

        return $toReturn;
    }

    /**
     * @param array $identifier
     * @param null  $shardingKey
     *
     * @return $this
     */
    public function remove(array $identifier, $shardingKey = null)
    {
        $this->schemaManager->remove(
            $this->tableName,
            array_merge(
                $this->mapIdentifier($identifier),
                array($this->nameField=>array_keys($this->fields))
            ),
            $shardingKey
        );

        return $this;
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     *
     * @return array
     * @throws \Exception
     */
    public function save($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null)
    {
        $datasWithoutIdentifiers=array();
        foreach ($datas as $key => $value) {
            if (isset($this->mapping[$key])) {
                $identifier[$key] = $value;
            } else {
                $datasWithoutIdentifiers[$key]=$value;
            }
        }
        $datas = $datasWithoutIdentifiers;
        //Get new identifiers
        $mappedIdentifier = $this->mapIdentifier($identifier);

        //Extract sharding key from identifier
        $shardingKey = null;
        if (isset($mappedIdentifier['sharding_key'])) {
            $shardingKey = $mappedIdentifier['sharding_key'];
            unset($mappedIdentifier['sharding_key']);
        }

        //Get Connection and table name
        $connection = $this->connectionManager->getConnection(
            $this->schemaManager->getConnectionName($this->tableName, $shardingKey, true),
            true
        );
        $shardingIdentifier = $this->schemaManager->getShardingIdentifier($this->tableName, $shardingKey);
        $tableName = $this->tableName;
        if (isset($shardingIdentifier)) {
            $tableName = sprintf('%1$s_%2$s', $this->tableName, $shardingIdentifier);
        }

        //First, remove all
        if (false === empty($identifier)) {
            $this->remove($identifier);
        }

        //then Insert
        $affectedRows = 0;
        foreach ($datas as $key => $value) {
            if (false === array_key_exists($key, $identifier)) {
                $connection->insert(
                    $tableName,
                    array_merge(
                        $mappedIdentifier,
                        array(
                            $this->nameField=>$key,
                            $this->valueField=>$value,
                        )
                    )
                );
                $affectedRows++;
            }
        }

        return $identifier;
    }

    /**
     * @param array $datas
     * @param array $identifier
     * @param null  $shardingKey
     * @param null  $affectedRows
     *
     * @return int
     */
    public function saveAfterLoading($datas, array $identifier = array(), $shardingKey = null, &$affectedRows = null)
    {
        return $this->save($datas, $identifier, $shardingKey, $affectedRows);
    }

    /**
     * @param array $identifier
     * @return array
     * @throws \RuntimeException
     */
    protected function mapIdentifier(array $identifier)
    {
        $mappedIdentifier = array();
        foreach ($identifier as $key => $value) {
            if (false === isset($this->mapping[$key])) {
                throw new \RuntimeException('Missing field mapping key : '.$key);
            }
            $mappedIdentifier[$this->mapping[$key]] = $value;
        }

        return $mappedIdentifier;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component;

use Yucca\Component\Selector\SourceFactory\SelectorSourceFactoryInterface;

/**
 * Class SelectorManager
 * @package Yucca\Component
 */
class SelectorManager
{
    /**
     * @var array
     */
    protected $selectorSourceConfig;

    /**
     * @var SelectorSourceFactoryInterface[]
     */
    protected $selectorSourceFactories;

    /**
     * @var \Yucca\Component\Selector\Source\SelectorSourceInterface[]
     */
    protected $sources;

    /**
     * @param array $selectorSourceConfig
     */
    public function __construct(array $selectorSourceConfig)
    {
        $this->selectorSourceConfig = $selectorSourceConfig;
    }

    /**
     * Add a source factory to the pool
     * @param string                                                                 $selectorSourceFactoryName
     * @param \Yucca\Component\Selector\SourceFactory\SelectorSourceFactoryInterface $selectorSourceFactory
     * @return \Yucca\Component\SelectorManager
     */
    public function addSelectorSourceFactory($selectorSourceFactoryName, SelectorSourceFactoryInterface $selectorSourceFactory)
    {
        $this->selectorSourceFactories[$selectorSourceFactoryName] = $selectorSourceFactory;

        return $this;
    }

    /**
     * @param string $selectorClassName
     * @return \Yucca\Component\Selector\SelectorAbstract
     * @throws \Exception
     */
    public function getSelector($selectorClassName)
    {
        if (false === isset($this->selectorSourceConfig[$selectorClassName]['sources'])) {
            throw new \Exception("Selector $selectorClassName is not configured");
        }

        $selectorSources = array();
        foreach ($this->selectorSourceConfig[$selectorClassName]['sources'] as $selectorSourceName) {
            $selectorSources[] = $this->getSource($selectorSourceName);
        }

        if (1 === count($selectorSources)) {
            return new $selectorClassName(current($selectorSources));
        } else {
            return new $selectorClassName($this->getFactory('chain')->getSource($selectorSources));
        }
    }

    /**
     * get factory by its type
     * @param $type
     * @return Selector\SourceFactory\SelectorSourceFactoryInterface
     * @throws \Exception
     */
    protected function getFactory($type)
    {
        if (isset($this->selectorSourceFactories[$type])) {
            return $this->selectorSourceFactories[$type];
        } else {
            throw new \Exception("Factory \"$type\" not foud");
        }
    }

    /**
     * Get a source by it's name
     * @param $selectorSourceName
     * @return \Yucca\Component\Selector\Source\SelectorSourceInterface
     * @throws \InvalidArgumentException
     */
    protected function getSource($selectorSourceName)
    {
        if (false === isset($this->sources[$selectorSourceName])) {
            $this->sources[$selectorSourceName] = $this->getFactory($selectorSourceName)->getSource();
        }

        return $this->sources[$selectorSourceName];
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Yucca\Component;

use Yucca\Model\ModelInterface;

/**
 * Class EntityManager
 * @package Yucca\Component
 */
class EntityManager
{
    protected $defaultIdKey;

    /**
     * @var \Yucca\Component\MappingManager
     */
    protected $mappingManager;

    /**
     * @var \Yucca\Component\SelectorManager
     */
    protected $selectorManager;

    /**
     * @param string $defaultIdKey
     */
    public function __construct($defaultIdKey = 'id')
    {
        $this->defaultIdKey = $defaultIdKey;
    }

    /**
     * @param \Yucca\Component\MappingManager $mappingManager
     */
    public function setMappingManager(MappingManager $mappingManager)
    {
        $this->mappingManager = $mappingManager;
    }

    /**
     * @param \Yucca\Component\SelectorManager $selectorManager
     */
    public function setSelectorManager(SelectorManager $selectorManager)
    {
        $this->selectorManager = $selectorManager;
    }

    /**
     * @return \Yucca\Component\SelectorManager
     */
    public function getSelectorManager()
    {
        return $this->selectorManager;
    }

    /**
     * Use this when retrieving a serialized object (from session, for example)
     * It bounds yucca's manager into the object
     *
     * @param ModelInterface $model
     *
     * @return EntityManager
     */
    public function refresh(ModelInterface $model)
    {
        return $model->refresh($this->mappingManager, $this->selectorManager, $this);
    }

    /**
     * @param string      $entityClassName
     * @param array|mixed $identifier
     * @param mixed       $shardingKey
     * @throws \Exception
     * @return \Yucca\Model\ModelInterface
     */
    public function load($entityClassName, $identifier, $shardingKey = null)
    {
        //If $identifier isn't an array, assumed it's the id value
        if (false === is_array($identifier)) {
            $identifier = array($this->defaultIdKey=>$identifier);
        }

        //create object
        if (false === class_exists($entityClassName)) {
            throw new \Exception("Entity class $entityClassName not found.");
        }

        /**
         * @var \Yucca\Model\ModelInterface $toReturn
         */
        $toReturn = new $entityClassName();

        //@Fixme : generate proxy like doctrine
        if (false === ($toReturn instanceof ModelInterface)) {
            throw new \Exception('Entity class '.$entityClassName.' must implement \Yucca\Model\ModelInterface.');
        }

        $this->initModel($toReturn);
        $toReturn->setYuccaIdentifier($identifier, $shardingKey);

        return $toReturn;
    }

    /**
     * @param ModelInterface $model
     *
     * @return $this
     */
    public function save(ModelInterface $model)
    {
        $this->initModel($model);

        $model->save();

        return $this;
    }

    /**
     * @param ModelInterface $model
     *
     * @return $this
     */
    public function remove(ModelInterface $model)
    {
        $this->initModel($model);

        $model->remove();

        return $this;
    }

    /**
     * @param ModelInterface $model
     * @param array|mixed    $identifier
     *
     * @return $this
     */
    public function resetModel(ModelInterface $model, $identifier)
    {
        $model->reset($identifier);

        return $this;
    }

    /**
     * @param \Yucca\Model\ModelInterface $model
     * @return EntityManager
     */
    protected function initModel(ModelInterface $model)
    {
        $model->setYuccaMappingManager($this->mappingManager)
            ->setYuccaSelectorManager($this->selectorManager)
            ->setYuccaEntityManager($this);

        return $this;
    }
}


<?php
/*
 * This file was delivered to you as part of the Yucca package.
 *
 * (c) Rémi JANOT <r.janot@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Yucca\Component\Selector;

use \Yucca\Component\Selector\Exception\NoDataException;
use \Yucca\Component\Selector\Source\SelectorSourceInterface;
use Yucca\Component\Selector\Exception\PointerException;

/**
 * Class SelectorAbstract
 * @package Yucca\Component\Selector
 */
abstract class SelectorAbstract implements SelectorInterface
{
    protected $options = array();
    protected $criterias = array();
    protected $isQueryPrepared = false;
    protected $idsArray = array();
    protected $idsCount = null;
    protected $orderBy = null;
    protected $groupBy = null;
    protected $offset = null;
    protected $limit = null;

    /**
     * @var SelectorSourceInterface
     */
    protected $source;

    protected $idFields;
    protected $shardingKeyField;
    protected $table;

    protected $SELECTOR_NAME;
    private $VERSION = '1';
    protected $SPECIFIC_VERSION_OFFSET = '1';

    /**
     * @param Source\SelectorSourceInterface $source
     */
    public function __construct(SelectorSourceInterface $source = null)
    {
        $this->SELECTOR_NAME = get_class($this);

        $this->setSource($source);
    }

    /**
     * Set the source used by the selector
     * @param SelectorSourceInterface $source
     * @return \Yucca\Component\Selector\SelectorAbstract
     */
    public function setSource(SelectorSourceInterface $source = null)
    {
        $this->source = $source;

        return $this;
    }

    /**
     *
     */
    public function invalidateGlobal()
    {
        $this->source->invalidateGlobal($this->options);
    }

    /**
     * @param array $ids
     * @param array $options
     */
    public function setIdsArray($ids, $options)
    {
        $this->idsArray = $ids;
    }

    /**
     * Get content
     * @return array
     */
    public function getIds()
    {
        $this->prepareQuery(false);

        return $this->idsArray;
    }

    /**
     * Reset criterias
     * @return \Yucca\Component\Selector\SelectorAbstract
     */
    public function reset()
    {
        $this->criterias = array();

        return $this;
    }

    /**
     * Implements Countable interface - return
     * @return int
     */
    public function count()
    {
        $this->prepareCount(true);

        return $this->idsCount;
    }

    /**
     * Implements Iterator interface
     * @throws PointerException
     * @return int
     */
    public function current()
    {
        $this->prepareQuery();

        if (false !== current($this->idsArray)) {
            return current($this->idsArray);
        } else {
            throw new PointerException('Can\'t retrieve the current element');
        }
    }

    /**
     * Not applicable here, but for abstract purpose
     * @return null
     */
    public function currentShardingKey()
    {
        return isset($this->options[SelectorSourceInterface::SHARDING_KEY]) ? $this->options[SelectorSourceInterface::SHARDING_KEY] : null;
    }

    /**
     * Implements Iterator interface
     * @return mixed
     */
    public function key()
    {
        $this->prepareQuery();

        return key($this->idsArray);
    }

    /**
     * Implements Iterator interface
     */
    public function next()
    {
        $this->prepareQuery();
        next($this->idsArray);
    }

    /**
     * Implements Iterator interface
     */
    public function rewind()
    {
        $this->prepareQuery();
        reset($this->idsArray);
    }

    /**
     * Implements Iterator interface
     * @return boolean
     */
    public function valid()
    {
        $this->prepareQuery();

        return ( false !== current($this->idsArray) );
    }

    /**
     * @param mixed $value
     *
     * @return $this
     */
    public function orderBy($value)
    {
        $this->orderBy = $value;

        return $this;
    }

    /**
     * @param mixed $value
     *
     * @return $this
     */
    public function offset($value)
    {
        $this->offset = $value;

        return $this;
    }

    /**
     * @param mixed $value
     *
     * @return $this
     */
    public function limit($value)
    {
        if (0 === preg_match('/^([0-9]+)(,[0-9]+)?$/', $value)) {
            throw new \InvalidArgumentException($value.' doesn\'t match limit pattern');
        }
        $this->limit = $value;

        return $this;
    }

    /**
     * @param array $criteria
     */
    public function setCriteria(array $criteria)
    {
        foreach ($criteria as $field => $value) {
            $this->criterias[$field][] = $value;
        }
    }

    /**
     * prepare ids : there the criterias are fetched
     * @throws Exception\NoDataException
     * @return \Yucca\Component\Selector\SelectorAbstract
     */
    protected function prepareQuery()
    {
        if (false === $this->isQueryPrepared) {
            $options = array(
                SelectorSourceInterface::RESULT => SelectorSourceInterface::RESULT_IDENTIFIERS,
                SelectorSourceInterface::LIMIT => $this->limit,
                SelectorSourceInterface::OFFSET => $this->offset,
                SelectorSourceInterface::ORDERBY => $this->orderBy,
                SelectorSourceInterface::GROUPBY => $this->groupBy,
            );
            $this->setIdsArray(
                $this->source->loadIds(
                    $this->criterias,
                    array_merge($this->options, $options)
                ),
                array_merge($this->options, $options)
            );
            $this->isQueryPrepared = true;
        }

        return $this;
    }

    /**
     * prepare count : there the criterias are fetched
     * @throws Exception\NoDataException
     * @return \Yucca\Component\Selector\SelectorAbstract
     */
    protected function prepareCount()
    {
        if (is_null($this->idsCount)) {
            $options = array(
                SelectorSourceInterface::RESULT => SelectorSourceInterface::RESULT_COUNT,
                SelectorSourceInterface::GROUPBY => $this->groupBy,
            );
            $this->idsCount = $this->source->loadIds(
                $this->criterias,
                array_merge($this->options, $options)
            );
        }

        return $this;
    }
}


<?php

/**
 * @license LGPLv3, http://www.gnu.org/licenses/lgpl.html
 * @copyright Metaways Infosystems GmbH, 2013
 * @copyright Aimeos (aimeos.org), 2015-2018
 * @package MW
 * @subpackage Container
 */

namespace Aimeos\MW\Container;

/**
 * Implementation of PHPExcel containers.
 *
 * @package MW
 * @subpackage Container
 */
class PHPExcel
	extends \Aimeos\MW\Container\Base
	implements \Aimeos\MW\Container\Iface
{
	private $container;
	private $format;

	/**
	 * Opens an existing container or creates a new one.
	 *
	 * @param string $resourcepath Path to the resource like a file
	 * @param string $format Format of the content objects inside the container
	 * @param array $options Associative list of key/value pairs for configuration
	 */
	public function __construct( $resourcepath, $format, array $options = [] )
	{
		if( file_exists( $resourcepath ) )
		{
			$type = \PHPExcel_IOFactory::identify( $resourcepath );
			$reader = \PHPExcel_IOFactory::createReader( $type );
			$this->container = $reader->load( $resourcepath );
		}
		else
		{
			$this->container = new \PHPExcel();
			$this->container->removeSheetByIndex( 0 );

			switch( $format )
			{
				case 'Excel5':
					$resourcepath .= '.xls';
					break;
				case 'Excel2003XML':
					$resourcepath .= '.xml';
					break;
				case 'Excel2007':
					$resourcepath .= '.xlsx';
					break;
				case 'OOCalc':
					$resourcepath .= '.ods';
					break;
				case 'SYLK':
					$resourcepath .= '.slk';
					break;
				case 'Gnumeric':
					$resourcepath .= '.gnumeric';
					break;
				case 'CSV':
					$resourcepath .= '.csv';
					break;
			}
		}

		parent::__construct( $resourcepath, $options );

		$this->iterator = $this->container->getWorksheetIterator();

		$this->resourcepath = $resourcepath;
		$this->format = $format;
	}

	/**
	 * Creates a new content object.
	 *
	 * @param string $name Name of the content
	 * @return \Aimeos\MW\Container\Content\Iface New content object
	 */
	public function create( $name )
	{
		$sheet = $this->container->createSheet();
		$sheet->setTitle( $name );

		return new \Aimeos\MW\Container\Content\PHPExcel( $sheet, $name, $this->getOptions() );
	}

	/**
	 * Adds content data to the container.
	 *
	 * @param \Aimeos\MW\Container\Content\Iface $content Content object
	 */
	public function add( \Aimeos\MW\Container\Content\Iface $content )
	{
		// was already added to the PHPExcel object by createSheet()
	}

	/**
	 * Returns the element specified by its name.
	 *
	 * @param string $name Name of the content object that should be returned
	 * @return \Aimeos\MW\Container\Content\Iface Content object
	 */
	public function get( $name )
	{
		if( ( $sheet = $this->container->getSheetByName( $name ) ) === null ) {
			throw new \Aimeos\MW\Container\Exception( sprintf( 'No sheet "%1$s" available', $name ) );
		}

		return new \Aimeos\MW\Container\Content\PHPExcel( $sheet, $sheet->getTitle(), $this->getOptions() );
	}

	/**
	 * Cleans up and saves the container.
	 */
	public function close()
	{
		$writer = \PHPExcel_IOFactory::createWriter( $this->container, $this->format );
		$writer->save( $this->resourcepath );
	}

	/**
	 * Return the current element.
	 *
	 * @return \Aimeos\MW\Container\Content\Iface Content object with PHPExcel sheet
	 */
	public function current()
	{
		$sheet = $this->iterator->current();

		return new \Aimeos\MW\Container\Content\PHPExcel( $sheet, $sheet->getTitle(), $this->getOptions() );
	}

	/**
	 * Returns the key of the current element.
	 *
	 * @return integer Index of the PHPExcel sheet
	 */
	public function key()
	{
		return $this->iterator->key();
	}

	/**
	 * Moves forward to next PHPExcel sheet.
	 */
	public function next()
	{
		return $this->iterator->next();
	}

	/**
	 * Rewinds to the first PHPExcel sheet.
	 */
	public function rewind()
	{
		return $this->iterator->rewind();
	}

	/**
	 * Checks if the current position is valid.
	 *
	 * @return boolean True on success or false on failure
	 */
	public function valid()
	{
		return $this->iterator->valid();
	}
}


<?php

/**
 * @license LGPLv3, http://www.gnu.org/licenses/lgpl.html
 * @copyright Metaways Infosystems GmbH, 2013
 * @copyright Aimeos (aimeos.org), 2015-2018
 * @package MW
 * @subpackage Container
 */

namespace Aimeos\MW\Container\Content;

/**
 * Implementation of the PHPExcel content object.
 *
 * @package MW
 * @subpackage Container
 */
class PHPExcel
	extends \Aimeos\MW\Container\Content\Base
	implements \Aimeos\MW\Container\Content\Iface
{
	private $sheet;
	private $iterator;

	/**
	 * Initializes the PHPExcel content object.
	 *
	 * @param \PHPExcel_Worksheet $sheet PHPExcel sheet
	 * @param array $options Associative list of key/value pairs for configuration
	 */
	public function __construct( \PHPExcel_Worksheet $sheet, $name, array $options = [] )
	{
		parent::__construct( $sheet, $name, $options );

		$this->sheet = $sheet;
		$this->iterator = $sheet->getRowIterator();
	}

	/**
	 * Cleans up and saves the content.
	 * Does nothing for PHPExcel sheets.
	 */
	public function close()
	{
	}

	/**
	 * Adds a row to the content object.
	 *
	 * @param mixed $data Data to add
	 */
	public function add( $data )
	{
		$columnNum = 0;
		$rowNum = $this->iterator->current()->getRowIndex();

		foreach( (array) $data as $value ) {
			$this->sheet->setCellValueByColumnAndRow( $columnNum++, $rowNum, $value );
		}

		$this->iterator->next();
	}

	/**
	 * Return the current row.
	 *
	 * @return array List of values
	 */
	public function current()
	{
		if( $this->iterator->valid() === false ) {
			return null;
		}

		$iterator = $this->iterator->current()->getCellIterator();
		$iterator->setIterateOnlyExistingCells( false );

		$result = [];

		foreach( $iterator as $cell ) {
			$result[] = $cell->getValue();
		}

		return $result;
	}

	/**
	 * Returns the key of the current row.
	 *
	 * @return integer Position within the PHPExcel sheet
	 */
	public function key()
	{
		return $this->iterator->key();
	}

	/**
	 * Moves forward to next row.
	 */
	public function next()
	{
		$this->iterator->next();
	}

	/**
	 * Resets the current row to the beginning of the sheet.
	 */
	public function rewind()
	{
		$this->iterator->rewind();
	}

	/**
	 * Checks if the current position is valid.
	 *
	 * @return boolean True on success or false on failure
	 */
	public function valid()
	{
		return $this->iterator->valid();
	}
}


<?php
/**
 * Created by Larakit.
 * Link: http://github.com/larakit
 * User: Alexey Berdnikov
 * Date: 01.08.16
 * Time: 12:05
 */

namespace Larakit;

class TelegramBot {

    static protected $messages = [];

    static function sendError(\Exception $e, $channel_code = null) {
        TelegramBot::add(str_repeat('=', 50));
        TelegramBot::add('#error: ' . ($e->getMessage() ? : '[Страница не найдена]'));
        TelegramBot::add(str_repeat('=', 50));
        TelegramBot::add('САЙТ: ' . \Config::get('app.url'));
        TelegramBot::add('ГДЕ: ' . larasafepath($e->getFile()) . ':' . $e->getLine());
        if(\Auth::user()) {
            TelegramBot::add('КТО: ' . \Auth::user()->name);
        } else {
            TelegramBot::add('КТО: гость ' . \Request::ip());
        }
        TelegramBot::add('URL: ' . \Request::fullUrl());
        TelegramBot::add(str_repeat('-', 80));
        static::send($channel_code ? : env('error'));
    }

    static function add($str) {
        if(!is_scalar($str)) {
            $str = var_export($str, true);
        }
        //        $str              = explode(PHP_EOL, $str);
        //        $str              = array_map(function ($item) {
        //            return mb_substr($item, 0, 1000);
        //        }, $str);
        static::$messages[] = $str;
    }

    const MAX = 1024;

    //    const MAX = 100;

    static function send($channel_code = null) {
        try {
            if(is_numeric($channel_code)) {
                $channel_id = $channel_code;
            } else {
                $channel_id = env('telegram.' . $channel_code);
                $channel_id = $channel_id ? : env('telegram');
            }
            if(!$channel_id) {
                echo 'No channel';

                return;
            }
            if(is_array($channel_code)) {
                foreach($channel_code as $_code) {
                    self::send($_code);
                }

                return;
            }
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 3);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            $partials = [];
            $current  = '';
            foreach(static::$messages as $message) {
                //если еще есть куда складывать
                if(mb_strlen($current) + mb_strlen($message) < self::MAX) {
                    $current .= PHP_EOL . $message;
                } else {
                    //если складывать некуда, то сбросим текущий слот в очередь
                    $partials[] = $current;
                    //очистим его
                    $current = '';
                    if(mb_strlen($message) > self::MAX) {
                        $strlen = mb_strlen($message);
                        while($strlen) {
                            $partials[] = mb_substr($message, 0, self::MAX, "UTF-8");
                            $message    = mb_substr($message, self::MAX, mb_strlen($message), "UTF-8");
                            $strlen     = mb_strlen($message);
                        }
                    } else {
                        $current = $message;
                    }
                }

            }
            $partials[] = $current;

            foreach($partials as $partial) {
                $params = http_build_query([
                    'disable_web_page_preview' => 'true',
                    'parse_mode'               => 'HTML',
                    'chat_id'                  => $channel_id,
                    'text'                     => $partial,
                ]);
                $url    = 'https://api.telegram.org/bot' . env('telegram.key') . '/sendMessage?';
                curl_setopt($ch, CURLOPT_URL, $url . $params);
                curl_exec($ch);
            }
            curl_close($ch);
        } catch(\Exception $e) {
            echo $e->getMessage();
        }
    }

    static function emoticon($code) {
        $emoticons = $code;

        return json_decode('"' . $emoticons . '"') . ' ';
    }

    static function headerIcon($code, $text, $cnt = 1) {
        $icon = self::emoticon($code) . ' ';

        return TelegramBot::add(str_repeat($icon,$cnt) . $text);
    }

    static function headerIconKey($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x94\x91", $text, $cnt);
    }

    static function headerIconHour1($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x90", $text, $cnt);
    }

    static function headerIconHour2($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x90", $text, $cnt);
    }

    static function headerIconHour3($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x92", $text, $cnt);
    }

    static function headerIconHour4($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x93", $text, $cnt);
    }

    static function headerIconHour5($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x94", $text, $cnt);
    }

    static function headerIconHour6($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x95", $text, $cnt);
    }

    static function headerIconHour7($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x96", $text, $cnt);
    }

    static function headerIconHour8($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x97", $text, $cnt);
    }

    static function headerIconHour9($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x98", $text, $cnt);
    }

    static function headerIconHour10($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x99", $text, $cnt);
    }

    static function headerIconHour11($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x9A", $text, $cnt);
    }

    static function headerIconHour12($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x95\x9B", $text, $cnt);
    }

    static function headerIconFire($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x94\xA5", $text, $cnt);
    }

    static function headerIconKeyOpen($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x94\x93", $text, $cnt);
    }

    static function headerIconEdit($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9C\x8F", $text, $cnt);
    }

    static function headerIconAdd($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9E\x95", $text, $cnt);
    }

    static function headerIconRestore($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x8D\x83", $text, $cnt);
    }

    static function headerIconDelete($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9D\x8C", $text, $cnt);
    }

    static function headerIconBug($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x90\x9E", $text, $cnt);
    }

    static function headerIconThumbsUp($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x91\x8D", $text, $cnt);
    }

    static function headerIconThumbsDown($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x91\x8E", $text, $cnt);
    }

    static function headerIconMoney($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x92\xB5", $text, $cnt);
    }

    static function headerIconGraphUp($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x93\x88", $text, $cnt);
    }

    static function headerIconGraphDown($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x93\x89", $text, $cnt);
    }

    static function headerIconStick($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x93\x8C", $text, $cnt);
    }

    static function headerIconSpeaker($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x93\xA2", $text, $cnt);
    }

    static function headerIconLetter($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x93\xA8", $text, $cnt);
    }

    static function headerIconGlobe($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x8C\x8D", $text, $cnt);
    }

    static function headerIconPray($text, $cnt = 1) {
        return self::headerIcon("\xF0\x9F\x99\x8F", $text, $cnt);
    }

    static function headerIconClips($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9C\x82", $text, $cnt);
    }

    static function headerIconSnowflake($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9D\x84", $text, $cnt);
    }

    static function headerIconHeart($text, $cnt = 1) {
        return self::headerIcon("\xE2\x9D\xA4", $text, $cnt);
    }

}