<?php

namespace SMProxy;

use SMProxy\Log\Log;
use Swoole\Coroutine\Channel;
use SMProxy\MysqlPool\MySQLException;
use SMProxy\MysqlPacket\ErrorPacket;
use function SMProxy\Helper\array_iconv;

/**
 * Author: Louis Livi <574747417@qq.com>
 * Date: 2018/10/30
 * Time: 上午11:06.
 */
class Base extends Context
{
    /**
     * 携程执行处理异常.
     *
     * @param $function
     */
    protected static function go(\Closure $function)
    {
        if (-1 !== \Swoole\Coroutine::getuid()) {
            $pool = self::$pool[\Swoole\Coroutine::getuid()] ?? false;
        } else {
            $pool = false;
        }
        go(function () use ($function, $pool) {
            try {
                if ($pool) {
                    self::$pool[\Swoole\Coroutine::getuid()] = $pool;
                }
                $function();
                if ($pool) {
                    unset(self::$pool[\Swoole\Coroutine::getuid()]);
                }
            } catch (SMProxyException $SMProxyException) {
                self::writeErrorMessage($SMProxyException, 'system');
            } catch (MySQLException $MySQLException) {
                self::writeErrorMessage($MySQLException, 'mysql');
            }
        });
    }

    /**
     * 写入日志
     *
     * @param $exception
     * @param string $tag
     */
    protected static function writeErrorMessage($exception, string $tag = 'mysql')
    {
        $log = Log::getLogger($tag);
        $errLevel = $exception ->getCode() ? array_search($exception ->getCode(), Log::$levels) : 'warning';
        $log->$errLevel($exception->errorMessage());
        if (CONFIG['server']['swoole']['daemonize'] != true) {
            echo  '[' . ucfirst($errLevel) . '] ', $exception->errorMessage(), PHP_EOL;
        }
    }

    /**
     * 格式化配置项.
     *
     * @param array $_config
     *
     * @return array
     *
     * @throws \SMProxy\SMProxyException
     */
    public function parseDbConfig(array $_config)
    {
        $config = $_config['database'] ?? [];
        foreach ($config['databases'] as $key => $database) {
            if (isset($config['serverInfo'][$database['serverInfo']])) {
                $config['databases'][$key]['maxConns']      = floor(
                    eval('return ' . $config['databases'][$key]['maxConns'] . ';') / CONFIG['server']['swoole']['worker_num']
                );
                $config['databases'][$key]['maxSpareConns'] = floor(
                    eval('return ' . $config['databases'][$key]['maxSpareConns'] . ';') / CONFIG['server']['swoole']['worker_num']
                );
                $config['databases'][$key]['startConns']    = eval('return ' . $config['databases'][$key]['startConns'] . ';');
                $config['databases'][$key]['maxSpareExp']   = eval('return ' . $config['databases'][$key]['maxSpareExp'] . ';');
                foreach ($config['serverInfo'][$database['serverInfo']] as $s_key => $value) {
                    if (isset($config['account'][$value['account']])) {
                        $host = &$config['serverInfo'][$database['serverInfo']][$s_key]['host'];
                        if (is_array($host)) {
                            $host = $host[array_rand($host)];
                        }
                        if (!isset($config['databases'][$s_key])) {
                            $config['databases'][$s_key] = $config['databases'][$key];
                            $config['databases'][$s_key]['serverInfo'] =
                                $config['serverInfo'][$database['serverInfo']][$s_key];
                            $config['databases'][$s_key]['serverInfo']['account'] =
                                $config['account'][$value['account']];
                        }
                        $config['databases'][$s_key . DB_DELIMITER . $key] = $config['databases'][$key];
                        $config['databases'][$s_key . DB_DELIMITER . $key]['serverInfo'] =
                            $config['serverInfo'][$database['serverInfo']][$s_key];
                        $config['databases'][$s_key . DB_DELIMITER . $key]['serverInfo']['account'] =
                            $config['account'][$value['account']];
                    } else {
                        throw new SMProxyException('Config serverInfo->' . $s_key . '->account is not exists!');
                    }
                }
            } else {
                throw new SMProxyException('Config serverInfo key ' . $database['serverInfo'] . 'is not exists!');
            }
            unset($config['databases'][$key]);
        }
        return $config['databases'];
    }

    /**
     * 协程pop
     *
     * @param $chan
     * @param int $timeout
     *
     * @return bool
     */
    protected static function coPop(Channel $chan, int $timeout = 0)
    {
        if (version_compare(swoole_version(), '4.0.3', '>=')) {
            return $chan->pop($timeout);
        } else {
            if (0 == $timeout) {
                return $chan->pop();
            } else {
                $writes = [];
                $reads = [$chan];
                $result = $chan->select($reads, $writes, $timeout);
                if (false === $result || empty($reads)) {
                    return false;
                }
                $readChannel = $reads[0];
                return $readChannel->pop();
            }
        }
    }

    protected static function writeErrMessage(int $id, string $msg, int $errno = 0, $sqlState = 'HY000')
    {
        $err = new ErrorPacket();
        $err->packetId = $id;
        if ($errno) {
            $err->errno = $errno;
        }
        $err->sqlState = $sqlState;
        $err->message  = array_iconv($msg);

        return $err->write();
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Doctrine;

use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\Query\Expr\From;
use Doctrine\ORM\QueryBuilder;
use FOS\ElasticaBundle\Provider\PagerfantaPager;
use FOS\ElasticaBundle\Provider\PagerProviderInterface;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;

final class ORMPagerProvider implements PagerProviderInterface
{
    const ENTITY_ALIAS = 'a';

    /**
     * @var string
     */
    private $objectClass;

    /**
     * @var ManagerRegistry
     */
    private $doctrine;

    /**
     * @var array
     */
    private $baseOptions;

    /**
     * @var RegisterListenersService
     */
    private $registerListenersService;

    /**
     * @param ManagerRegistry $doctrine
     * @param RegisterListenersService $registerListenersService
     * @param string $objectClass
     * @param array $baseOptions
     */
    public function __construct(ManagerRegistry $doctrine, RegisterListenersService $registerListenersService, $objectClass, array $baseOptions)
    {
        $this->doctrine = $doctrine;
        $this->objectClass = $objectClass;
        $this->baseOptions = $baseOptions;
        $this->registerListenersService = $registerListenersService;
    }

    /**
     * {@inheritdoc}
     */
    public function provide(array $options = array())
    {
        $options = array_replace($this->baseOptions, $options);

        $manager = $this->doctrine->getManagerForClass($this->objectClass);
        $repository = $manager->getRepository($this->objectClass);

        $qb = \call_user_func([$repository, $options['query_builder_method']], self::ENTITY_ALIAS);

        // Ensure that the query builder has a sorting configured. Without a ORDER BY clause, the SQL standard does not
        // guarantee any order, which breaks the pagination (second page might use a different sorting that when retrieving
        // the first page).
        // If the QueryBuilder already has its own ordering, or the method returned a Query instead of a QueryBuilder, we
        // assume that the query already provides a proper sorting. This allows giving full control over sorting if wanted
        // when using a custom method.
        if ($qb instanceof QueryBuilder && empty($qb->getDQLPart('orderBy'))) {
            // When getting root aliases, the QueryBuilder normalizes all from parts to From objects, in case they were added as string using the low-level API.
            // This side-effect allows us to be sure to get only From objects in the next call.
            $qb->getRootAliases();

            /** @var From[] $fromClauses */
            $fromClauses = $qb->getDQLPart('from');

            foreach ($fromClauses as $fromClause) {
                $identifiers = $manager->getClassMetadata($fromClause->getFrom())->getIdentifierFieldNames();

                foreach ($identifiers as $identifier) {
                    $qb->addOrderBy($fromClause->getAlias().'.'.$identifier);
                }
            }
        }

        $pager = new PagerfantaPager(new Pagerfanta(new DoctrineORMAdapter($qb)));

        $this->registerListenersService->register($manager, $pager, $options);

        return $pager;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Serializer;

use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface as JMSSerializer;
use Symfony\Component\Serializer\SerializerInterface;

class Callback
{
    protected $serializer;
    protected $groups = [];
    protected $version;
    protected $serializeNull;

    /**
     * @param $serializer
     */
    public function setSerializer($serializer)
    {
        $this->serializer = $serializer;
        if (!method_exists($this->serializer, 'serialize')) {
            throw new \RuntimeException('The serializer must have a "serialize" method.');
        }
    }

    /**
     * @param array $groups
     */
    public function setGroups(array $groups)
    {
        $this->groups = $groups;

        if (!empty($this->groups) && !$this->serializer instanceof SerializerInterface && !$this->serializer instanceof JMSSerializer) {
            throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer" or "Symfony\Component\Serializer\Serializer"');
        }
    }

    /**
     * @param $version
     */
    public function setVersion($version)
    {
        $this->version = $version;

        if ($this->version && !$this->serializer instanceof JMSSerializer) {
            throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
        }
    }

    /**
     * @param $serializeNull
     */
    public function setSerializeNull($serializeNull)
    {
        $this->serializeNull = $serializeNull;

        if (true === $this->serializeNull && !$this->serializer instanceof JMSSerializer) {
            throw new \RuntimeException('Setting null value serialization option requires using "JMS\Serializer\Serializer".');
        }
    }

    /**
     * @param $object
     *
     * @return mixed
     */
    public function serialize($object)
    {
        $context = $this->serializer instanceof JMSSerializer ? SerializationContext::create()->enableMaxDepthChecks() : [];

        if (!empty($this->groups)) {
            if ($context instanceof SerializationContext) {
                $context->setGroups($this->groups);
            } else {
                $context['groups'] = $this->groups;
            }
        }

        if ($this->version) {
            $context->setVersion($this->version);
        }

        if (!is_array($context)) {
            $context->setSerializeNull($this->serializeNull);
        }

        return $this->serializer->serialize($object, 'json', $context);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Command;

use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @internal
 */
final class ProgressClosureBuilder
{
    /**
     * Builds a loggerClosure to be called from inside the Provider to update the command
     * line.
     *
     * @param OutputInterface $output
     * @param string          $action
     * @param string          $index
     * @param string          $type
     * @param int             $offset
     *
     * @return callable
     */
    public static function build(OutputInterface $output, $action, $index, $type, $offset)
    {
        $progress = null;

        return function ($increment, $totalObjects, $message = null) use (&$progress, $output, $action, $index, $type, $offset) {
            if (null === $progress) {
                $progress = new ProgressBar($output, $totalObjects);
                $progress->start();
                $progress->setProgress($offset);
            }

            if (null !== $message) {
                $progress->clear();
                $output->writeln(sprintf('<info>%s</info> <error>%s</error>', $action, $message));
                $progress->display();
            }

            $progress->setMessage(sprintf('<info>%s</info> <comment>%s/%s</comment>', $action, $index, $type));
            $progress->advance($increment);
        };
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Persister;

use Elastica\Document;
use Elastica\Exception\BulkException;
use Elastica\Type;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
use Psr\Log\LoggerInterface;

/**
 * Inserts, replaces and deletes single documents in an elastica type
 * Accepts domain model objects and converts them to elastica documents.
 *
 * @author Thibault Duplessis <thibault.duplessis@gmail.com>
 */
class ObjectPersister implements ObjectPersisterInterface
{
    protected $type;
    protected $transformer;
    protected $objectClass;
    protected $fields;
    protected $logger;
    private $options;

    /**
     * @param Type                                $type
     * @param ModelToElasticaTransformerInterface $transformer
     * @param string                              $objectClass
     * @param array                               $fields
     */
    public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, array $fields, array $options = [])
    {
        $this->type = $type;
        $this->transformer = $transformer;
        $this->objectClass = $objectClass;
        $this->fields = $fields;
        $this->options = $options;
    }

    /**
     * {@inheritdoc}
     */
    public function handlesObject($object)
    {
        return $object instanceof $this->objectClass;
    }

    /**
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * {@inheritdoc}
     */
    public function insertOne($object)
    {
        $this->insertMany([$object]);
    }

    /**
     * {@inheritdoc}
     */
    public function replaceOne($object)
    {
        $this->replaceMany([$object]);
    }

    /**
     * {@inheritdoc}
     */
    public function deleteOne($object)
    {
        $this->deleteMany([$object]);
    }

    /**
     * {@inheritdoc}
     */
    public function deleteById($id, $routing = false)
    {
        $this->deleteManyByIdentifiers([$id], $routing);
    }

    /**
     * {@inheritdoc}
     */
    public function insertMany(array $objects)
    {
        $documents = [];
        foreach ($objects as $object) {
            $documents[] = $this->transformToElasticaDocument($object);
        }
        try {
            $this->type->addDocuments($documents, $this->options);
        } catch (BulkException $e) {
            $this->log($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function replaceMany(array $objects)
    {
        $documents = [];
        foreach ($objects as $object) {
            $document = $this->transformToElasticaDocument($object);
            $document->setDocAsUpsert(true);
            $documents[] = $document;
        }

        try {
            $this->type->updateDocuments($documents, $this->options);
        } catch (BulkException $e) {
            $this->log($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function deleteMany(array $objects)
    {
        $documents = [];
        foreach ($objects as $object) {
            $documents[] = $this->transformToElasticaDocument($object);
        }
        try {
            $this->type->deleteDocuments($documents);
        } catch (BulkException $e) {
            $this->log($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function deleteManyByIdentifiers(array $identifiers, $routing = false)
    {
        try {
            $this->type->deleteIds($identifiers, $routing);
        } catch (BulkException $e) {
            $this->log($e);
        }
    }

    /**
     * Transforms an object to an elastica document.
     *
     * @param object $object
     *
     * @return Document the elastica document
     */
    public function transformToElasticaDocument($object)
    {
        return $this->transformer->transform($object, $this->fields);
    }

    /**
     * Log exception if logger defined for persister belonging to the current listener, otherwise re-throw.
     *
     * @param BulkException $e
     *
     * @throws BulkException
     */
    private function log(BulkException $e)
    {
        if (!$this->logger) {
            throw $e;
        }

        $this->logger->error($e);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Command;

use FOS\ElasticaBundle\Index\TemplateResetter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

/**
 * Reset search indexes templates.
 */
final class ResetTemplatesCommand extends Command
{
    protected static $defaultName = 'fos:elastica:reset-templates';

    /** @var TemplateResetter */
    private $resetter;

    public function __construct(
        TemplateResetter $resetter
    ) {
        parent::__construct();

        $this->resetter = $resetter;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('fos:elastica:reset-templates')
            ->addOption(
                'index',
                null,
                InputOption::VALUE_REQUIRED,
                'The index template to reset. If no index template name specified than all templates will be reset'
            )
            ->addOption(
                'force-delete',
                null,
                InputOption::VALUE_NONE,
                'Delete all indexes that matches index templates patterns. ' .
                'Aware that pattern may match various indexes.'
            )
            ->setDescription('Reset search indexes templates')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $indexTemplate = $input->getOption('index');
        $deleteByPattern = $input->getOption('force-delete');

        if ($deleteByPattern) {
            $helper = $this->getHelper('question');
            $question = new ConfirmationQuestion('You are going to remove all template indexes. Are you sure?', false);

            if (!$helper->ask($input, $output, $question)) {
                return 1;
            }
        }

        if (null !== $indexTemplate) {
            $output->writeln(sprintf('<info>Resetting template</info> <comment>%s</comment>', $indexTemplate));
            $this->resetter->resetIndex($indexTemplate, $deleteByPattern);
        } else {
            $output->writeln('<info>Resetting all templates</info>');
            $this->resetter->resetAllIndexes($deleteByPattern);
        }

        return 0;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class ConfigSourcePass implements CompilerPassInterface
{
    const SOURCE_TYPE_INDEX_TEMPLATE = 'index_template';

    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('fos_elastica.config_manager')) {
            return;
        }

        $indexSources = [];
        $indexTemplateSources = [];
        foreach (array_keys($container->findTaggedServiceIds('fos_elastica.config_source')) as $id) {
            $tag = $container->findDefinition($id)->getTag('fos_elastica.config_source');
            if (isset($tag[0]['source']) && $tag[0]['source'] === self::SOURCE_TYPE_INDEX_TEMPLATE) {
                $indexTemplateSources[] = new Reference($id);
            } else {
                $indexSources[] = new Reference($id);
            }
        }

        $container->getDefinition('fos_elastica.config_manager')->replaceArgument(0, $indexSources);
        $container->getDefinition('fos_elastica.config_manager.index_templates')->replaceArgument(0, $indexTemplateSources);
    }
}


<?php

/**
 * This file is part of the FOSElasticaBundle project.
 *
 * (c) Tim Nagel <tim@nagel.com.au>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Configuration\Source;

use FOS\ElasticaBundle\Configuration\IndexTemplateConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;

/**
 * Returns index and type configuration from the container.
 */
class TemplateContainerSource implements SourceInterface
{
    /**
     * The internal container representation of information.
     *
     * @var array
     */
    private $configArray;

    public function __construct(array $configArray)
    {
        $this->configArray = $configArray;
    }

    /**
     * Should return all configuration available from the data source.
     *
     * @return IndexTemplateConfig[]
     */
    public function getConfiguration()
    {
        $indexes = array();
        foreach ($this->configArray as $config) {
            $types = $this->getTypes($config);
            $index = new IndexTemplateConfig($config['name'], $types, array(
                'elasticSearchName' => $config['elasticsearch_name'],
                'settings' => $config['settings'],
                'template' => $config['template'],
            ));

            $indexes[$config['name']] = $index;
        }

        return $indexes;
    }

    /**
     * Builds TypeConfig objects for each type.
     *
     * @param array $config
     *
     * @return array
     */
    protected function getTypes($config)
    {
        $types = array();

        if (isset($config['types'])) {
            foreach ($config['types'] as $typeConfig) {
                $types[$typeConfig['name']] = new TypeConfig(
                    $typeConfig['name'],
                    $typeConfig['mapping'],
                    $typeConfig['config']
                );
            }
        }

        return $types;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Doctrine;

use Doctrine\Common\Persistence\ManagerRegistry;
use FOS\ElasticaBundle\Provider\PagerfantaPager;
use FOS\ElasticaBundle\Provider\PagerProviderInterface;
use Pagerfanta\Adapter\DoctrineODMMongoDBAdapter;
use Pagerfanta\Pagerfanta;

final class MongoDBPagerProvider implements PagerProviderInterface
{
    /**
     * @var string
     */
    private $objectClass;

    /**
     * @var ManagerRegistry
     */
    private $doctrine;

    /**
     * @var array
     */
    private $baseOptions;

    /**
     * @var RegisterListenersService
     */
    private $registerListenersService;

    /**
     * @param ManagerRegistry $doctrine
     * @param RegisterListenersService $registerListenersService
     * @param string $objectClass
     * @param array $baseOptions
     */
    public function __construct(ManagerRegistry $doctrine, RegisterListenersService $registerListenersService, $objectClass, array $baseOptions)
    {
        $this->doctrine = $doctrine;
        $this->objectClass = $objectClass;
        $this->baseOptions = $baseOptions;
        $this->registerListenersService = $registerListenersService;
    }

    /**
     * {@inheritdoc}
     */
    public function provide(array $options = array())
    {
        $options = array_replace($this->baseOptions, $options);

        $manager = $this->doctrine->getManagerForClass($this->objectClass);
        $repository = $manager->getRepository($this->objectClass);

        $pager = new PagerfantaPager(new Pagerfanta(
            new DoctrineODMMongoDBAdapter(call_user_func([$repository, $options['query_builder_method']]))
        ));

        $this->registerListenersService->register($manager, $pager, $options);

        return $pager;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Configuration\Source;

use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;

/**
 * Returns index and type configuration from the container.
 */
class ContainerSource implements SourceInterface
{
    /**
     * The internal container representation of information.
     *
     * @var array
     */
    private $configArray;

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

    /**
     * Should return all configuration available from the data source.
     *
     * @return IndexConfig[]
     */
    public function getConfiguration()
    {
        $indexes = [];
        foreach ($this->configArray as $config) {
            $types = $this->getTypes($config);
            $index = new IndexConfig($config['name'], $types, [
                'elasticSearchName' => $config['elasticsearch_name'],
                'settings' => $config['settings'],
                'useAlias' => $config['use_alias'],
            ]);

            $indexes[$config['name']] = $index;
        }

        return $indexes;
    }

    /**
     * Builds TypeConfig objects for each type.
     *
     * @param array $config
     *
     * @return array
     */
    protected function getTypes($config)
    {
        $types = [];

        if (isset($config['types'])) {
            foreach ($config['types'] as $typeConfig) {
                $types[$typeConfig['name']] = new TypeConfig(
                    $typeConfig['name'],
                    $typeConfig['mapping'],
                    $typeConfig['config']
                );
                // TODO: handle prototypes..
            }
        }

        return $types;
    }
}


<?php
/*
 * This file is part of the OpCart software.
 *
 * (c) 2015, OpticsPlanet, Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Configuration;

/**
 * Index configuration trait class
 *
 * @author Dmitry Balabka <dmitry.balabka@intexsys.lv>
 */
trait IndexConfigTrait
{
    /**
     * The name of the index for ElasticSearch.
     *
     * @var string
     */
    private $elasticSearchName;

    /**
     * The internal name of the index. May not be the same as the name used in ElasticSearch,
     * especially if aliases are enabled.
     *
     * @var string
     */
    private $name;

    /**
     * An array of settings sent to ElasticSearch when creating the index.
     *
     * @var array
     */
    private $settings;

    /**
     * All types that belong to this index.
     *
     * @var TypeConfig[]
     */
    private $types;

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

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

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

    /**
     * @param string $typeName
     *
     * @return TypeConfig
     *
     * @throws \InvalidArgumentException
     */
    public function getType($typeName)
    {
        if (!array_key_exists($typeName, $this->types)) {
            throw new \InvalidArgumentException(sprintf('Type "%s" does not exist on index "%s"', $typeName, $this->name));
        }

        return $this->types[$typeName];
    }

    /**
     * @return \FOS\ElasticaBundle\Configuration\TypeConfig[]
     */
    public function getTypes()
    {
        return $this->types;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Provider;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

/**
 * References persistence providers for each index and type.
 */
class PagerProviderRegistry implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    /** @var array */
    private $providers = [];

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

    /**
     * Gets all registered providers.
     *
     * Providers will be indexed by "index/type" strings in the returned array.
     *
     * @return PagerProviderInterface[]
     */
    public function getAllProviders()
    {
        $providers = [];

        foreach ($this->providers as $index => $indexProviders) {
            foreach ($indexProviders as $type => $providerId) {
                $providers[sprintf('%s/%s', $index, $type)] = $this->container->get($providerId);
            }
        }

        return $providers;
    }

    /**
     * Gets all providers for an index.
     *
     * Providers will be indexed by "type" strings in the returned array.
     *
     * @param string $index
     *
     * @return PagerProviderInterface[]|
     *
     * @throws \InvalidArgumentException if no providers were registered for the index
     */
    public function getIndexProviders($index)
    {
        if (!isset($this->providers[$index])) {
            throw new \InvalidArgumentException(sprintf('No providers were registered for index "%s".', $index));
        }

        $providers = [];
        foreach ($this->providers[$index] as $type => $providerId) {
            $providers[$type] = $this->getProvider($index, $type);
        }

        return $providers;
    }

    /**
     * Gets the provider for an index and type.
     *
     * @param string $index
     * @param string $type
     *
     * @return PagerProviderInterface
     *
     * @throws \InvalidArgumentException if no provider was registered for the index and type
     */
    public function getProvider($index, $type)
    {
        if (!isset($this->providers[$index][$type])) {
            throw new \InvalidArgumentException(sprintf('No provider was registered for index "%s" and type "%s".', $index, $type));
        }

        return $this->container->get($this->providers[$index][$type]);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Subscriber;

use FOS\ElasticaBundle\Paginator\PaginatorAdapterInterface;
use FOS\ElasticaBundle\Paginator\PartialResultsInterface;
use Knp\Component\Pager\Event\ItemsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
{
    /**
     * @var RequestStack
     */
    private $requestStack;

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

    /**
     * @param ItemsEvent $event
     */
    public function items(ItemsEvent $event)
    {
        if ($event->target instanceof PaginatorAdapterInterface) {
            // Add sort to query
            $this->setSorting($event);

            /** @var $results PartialResultsInterface */
            $results = $event->target->getResults($event->getOffset(), $event->getLimit());

            $event->count = $results->getTotalHits();
            $event->items = $results->toArray();
            $aggregations = $results->getAggregations();
            if (null != $aggregations) {
                $event->setCustomPaginationParameter('aggregations', $aggregations);
            }

            $event->stopPropagation();
        }
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            'knp_pager.items' => ['items', 1],
        ];
    }

    /**
     * Adds knp paging sort to query.
     *
     * @param ItemsEvent $event
     */
    protected function setSorting(ItemsEvent $event)
    {
        $options = $event->options;
        $sortField = $this->getRequest()->get($options['sortFieldParameterName']);

        if (!$sortField && isset($options['defaultSortFieldName'])) {
            $sortField = $options['defaultSortFieldName'];
        }

        if (!empty($sortField)) {
            $event->target->getQuery()->setSort([
                $sortField => $this->getSort($sortField, $options),
            ]);
        }
    }

    protected function getSort($sortField, array $options = [])
    {
        $sort = [
            'order' => $this->getSortDirection($sortField, $options),
        ];

        if (isset($options['sortNestedPath'])) {
            $path = is_callable($options['sortNestedPath']) ?
                $options['sortNestedPath']($sortField) : $options['sortNestedPath'];

            if (!empty($path)) {
                $sort['nested_path'] = $path;
            }
        }

        if (isset($options['sortNestedFilter'])) {
            $filter = is_callable($options['sortNestedFilter']) ?
                $options['sortNestedFilter']($sortField) : $options['sortNestedFilter'];

            if (!empty($filter)) {
                $sort['nested_filter'] = $filter;
            }
        }

        return $sort;
    }

    protected function getSortDirection($sortField, array $options = [])
    {
        $dir = 'asc';
        $sortDirection = $this->getRequest()->get($options['sortDirectionParameterName']);

        if (empty($sortDirection) && isset($options['defaultSortDirection'])) {
            $sortDirection = $options['defaultSortDirection'];
        }

        if ('desc' === strtolower($sortDirection)) {
            $dir = 'desc';
        }

        // check if the requested sort field is in the sort whitelist
        if (isset($options['sortFieldWhitelist']) && !in_array($sortField, $options['sortFieldWhitelist'])) {
            throw new \UnexpectedValueException(sprintf('Cannot sort by: [%s] this field is not in whitelist', $sortField));
        }

        return $dir;
    }

    /**
     * @return Request|null
     */
    private function getRequest()
    {
        return $this->requestStack->getCurrentRequest();
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Paginator;

use Elastica\Query;
use Elastica\SearchableInterface;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;

/**
 * Allows pagination of \Elastica\Query.
 */
class TransformedPaginatorAdapter extends RawPaginatorAdapter
{
    private $transformer;

    /**
     * @param SearchableInterface                 $searchable  the object to search in
     * @param Query                               $query       the query to search
     * @param array                               $options
     * @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
     */
    public function __construct(SearchableInterface $searchable, Query $query, array $options, ElasticaToModelTransformerInterface $transformer)
    {
        parent::__construct($searchable, $query, $options);

        $this->transformer = $transformer;
    }

    /**
     * {@inheritdoc}
     */
    public function getResults($offset, $length)
    {
        return new TransformedPartialResults($this->getElasticaResults($offset, $length), $this->transformer);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Command;

use Elastica\Query;
use Elastica\Result;
use FOS\ElasticaBundle\Index\IndexManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Searches a type.
 */
class SearchCommand extends Command
{
    protected static $defaultName = 'fos:elastica:search';

    private $indexManager;

    public function __construct(IndexManager $indexManager)
    {
        parent::__construct();

        $this->indexManager = $indexManager;
    }

    protected function configure()
    {
        $this
            ->setName('fos:elastica:search')
            ->addArgument('type', InputArgument::REQUIRED, 'The type to search in')
            ->addArgument('query', InputArgument::REQUIRED, 'The text to search')
            ->addOption('index', null, InputOption::VALUE_REQUIRED, 'The index to search in')
            ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'The maximum number of documents to return', 20)
            ->addOption('show-field', null, InputOption::VALUE_REQUIRED, 'Field to show, null uses the first field')
            ->addOption('show-source', null, InputOption::VALUE_NONE, 'Show the documents sources')
            ->addOption('show-id', null, InputOption::VALUE_NONE, 'Show the documents ids')
            ->addOption('explain', null, InputOption::VALUE_NONE, 'Enables explanation for each hit on how its score was computed.')
            ->setDescription('Searches documents in a given type and index')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $indexName = $input->getOption('index');
        $index = $this->indexManager->getIndex($indexName ? $indexName : null);
        $type = $index->getType($input->getArgument('type'));
        $query = Query::create($input->getArgument('query'));
        $query->setSize($input->getOption('limit'));
        if ($input->getOption('explain')) {
            $query->setExplain(true);
        }

        $resultSet = $type->search($query);

        $output->writeLn(sprintf('Found %d results', $type->count($query)));
        foreach ($resultSet->getResults() as $result) {
            $output->writeLn($this->formatResult($result, $input->getOption('show-field'), $input->getOption('show-source'), $input->getOption('show-id'), $input->getOption('explain')));
        }
    }

    /**
     * @param Result $result
     * @param string $showField
     * @param string $showSource
     * @param string $showId
     * @param string $explain
     *
     * @return string
     */
    protected function formatResult(Result $result, $showField, $showSource, $showId, $explain)
    {
        $source = $result->getSource();
        if ($showField) {
            $toString = isset($source[$showField]) ? $source[$showField] : '-';
        } else {
            $toString = reset($source);
        }
        $string = sprintf('[%0.2f] %s', $result->getScore(), var_export($toString, true));
        if ($showSource) {
            $string = sprintf('%s %s', $string, json_encode($source));
        }
        if ($showId) {
            $string = sprintf('{%s} %s', $result->getId(), $string);
        }
        if ($explain) {
            $string = sprintf('%s %s', $string, json_encode($result->getExplanation()));
        }

        return $string;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Index;

use FOS\ElasticaBundle\Configuration\IndexConfigInterface;
use FOS\ElasticaBundle\Configuration\IndexTemplateConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;

class MappingBuilder
{
    /**
     * Builds mappings for an entire index.
     *
     * @param IndexConfigInterface $indexConfig
     *
     * @return array
     */
    public function buildIndexMapping(IndexConfigInterface $indexConfig)
    {
        $typeMappings = [];
        foreach ($indexConfig->getTypes() as $typeConfig) {
            $typeMappings[$typeConfig->getName()] = $this->buildTypeMapping($typeConfig);
        }

        $mapping = [];
        if (!empty($typeMappings)) {
            $mapping['mappings'] = $typeMappings;
        }
        // 'warmers' => $indexConfig->getWarmers(),

        $settings = $indexConfig->getSettings();
        if (!empty($settings)) {
            $mapping['settings'] = $settings;
        }

        return $mapping;
    }

    /**
     * Builds mappings for an entire index template.
     *
     * @param IndexTemplateConfig $indexTemplateConfig
     *
     * @return array
     */
    public function buildIndexTemplateMapping(IndexTemplateConfig $indexTemplateConfig)
    {
        $mapping = $this->buildIndexMapping($indexTemplateConfig);
        $mapping['template'] = $indexTemplateConfig->getTemplate();

        return $mapping;
    }

    /**
     * Builds mappings for a single type.
     *
     * @param TypeConfig $typeConfig
     *
     * @return array
     */
    public function buildTypeMapping(TypeConfig $typeConfig)
    {
        $mapping = $typeConfig->getMapping();

        if (null !== $typeConfig->getDynamicDateFormats()) {
            $mapping['dynamic_date_formats'] = $typeConfig->getDynamicDateFormats();
        }

        if (null !== $typeConfig->getDateDetection()) {
            $mapping['date_detection'] = $typeConfig->getDateDetection();
        }

        if (null !== $typeConfig->getNumericDetection()) {
            $mapping['numeric_detection'] = $typeConfig->getNumericDetection();
        }

        if ($typeConfig->getAnalyzer()) {
            $mapping['analyzer'] = $typeConfig->getAnalyzer();
        }

        if (null !== $typeConfig->getDynamic()) {
            $mapping['dynamic'] = $typeConfig->getDynamic();
        }

        if (isset($mapping['dynamic_templates']) and empty($mapping['dynamic_templates'])) {
            unset($mapping['dynamic_templates']);
        }

        $this->fixProperties($mapping['properties']);
        if (!$mapping['properties']) {
            unset($mapping['properties']);
        }

        if ($typeConfig->getModel()) {
            $mapping['_meta']['model'] = $typeConfig->getModel();
        }

        unset($mapping['_parent']['identifier'], $mapping['_parent']['property']);

        if (empty($mapping)) {
            // Empty mapping, we want it encoded as a {} instead of a []
            $mapping = new \ArrayObject();
        }

        return $mapping;
    }

    /**
     * Fixes any properties and applies basic defaults for any field that does not have
     * required options.
     *
     * @param $properties
     */
    private function fixProperties(&$properties)
    {
        foreach ($properties as $name => &$property) {
            unset($property['property_path']);

            if (!isset($property['type'])) {
                $property['type'] = 'text';
            }
            if (isset($property['fields'])) {
                $this->fixProperties($property['fields']);
            }
            if (isset($property['properties'])) {
                $this->fixProperties($property['properties']);
            }
        }
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\DependencyInjection\Compiler;

use FOS\ElasticaBundle\Persister\PagerPersisterInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RegisterPagerPersistersPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('fos_elastica.pager_persister_registry')) {
            return;
        }

        $registry = $container->getDefinition('fos_elastica.pager_persister_registry');

        $nameToServiceIdMap = [];
        foreach ($container->findTaggedServiceIds('fos_elastica.pager_persister', true) as $id => $attributes) {
            foreach ($attributes as $attribute) {
                if (!isset($attribute['persisterName'])) {
                    throw new \InvalidArgumentException(sprintf('Elastica pager persister "%s" must specify the "persisterName" attribute.', $id));
                }

                $persisterName = $attribute['persisterName'];

                if (isset($nameToServiceIdMap[$persisterName])) {
                    throw new \InvalidArgumentException(sprintf(
                        'Cannot register pager persister "%s". The pager persister "%s" has been registered for same name "%s"',
                        $id,
                        $nameToServiceIdMap[$persisterName],
                        $persisterName
                    ));
                }

                $persisterDef = $container->getDefinition($id);
                if (!$persisterDef->getFactory() && $persisterDef->getClass()) {
                    // You are on your own if you use a factory to create a persister.
                    // It would fail in runtime if the factory does not return a proper persister.
                    $this->assertClassImplementsPagerPersisterInterface($id, $container->getParameterBag()->resolveValue($persisterDef->getClass()));
                }

                if (!$persisterDef->isPublic()) {
                    throw new \InvalidArgumentException(sprintf('Elastica pager persister "%s" must be a public service', $id));
                }

                $nameToServiceIdMap[$persisterName] = $id;
            }
        }

        $registry->replaceArgument(0, $nameToServiceIdMap);
    }

    /**
     * @param $persisterId
     * @param $persisterClass
     *
     * @throws \InvalidArgumentException if persister service does not implement ObjectPersisterInterface
     */
    private function assertClassImplementsPagerPersisterInterface($persisterId, $persisterClass)
    {
        $rc = new \ReflectionClass($persisterClass);

        if (!$rc->implementsInterface(PagerPersisterInterface::class)) {
            throw new \InvalidArgumentException(sprintf(
                'Elastica pager persister "%s" with class "%s" must implement "%s".',
                $persisterId,
                $persisterClass,
                PagerPersisterInterface::class
            ));
        }
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

/**
 * Logger for the Elastica.
 *
 * The {@link logQuery()} method is configured as the logger callable in the
 * service container.
 *
 * @author Gordon Franke <info@nevalon.de>
 */
class ElasticaLogger extends AbstractLogger
{
    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var array
     */
    protected $queries = [];

    /**
     * @var bool
     */
    protected $debug;

    /**
     * Constructor.
     *
     * @param LoggerInterface|null $logger The Symfony logger
     * @param bool                 $debug
     */
    public function __construct(LoggerInterface $logger = null, $debug = false)
    {
        $this->logger = $logger;
        $this->debug = $debug;
    }

    /**
     * Logs a query.
     *
     * @param string $path       Path to call
     * @param string $method     Rest method to use (GET, POST, DELETE, PUT)
     * @param array|string $data Arguments
     * @param float  $queryTime  Execution time (in seconds)
     * @param array  $connection Host, port, transport, and headers of the query
     * @param array  $query      Arguments
     * @param int    $engineTime
     * @param int    $itemCount
     */
    public function logQuery($path, $method, $data, $queryTime, $connection = [], $query = [], $engineTime = 0, $itemCount = 0)
    {
        $executionMS = $queryTime * 1000;

        if ($this->debug) {
            $e = new \Exception();
            if (is_string($data)) {
                $jsonStrings = explode("\n", $data);
                $data = [];
                foreach ($jsonStrings as $json) {
                    if ($json != '') {
                        $data[] = json_decode($json, true);
                    }
                }
            } else {
                $data = [$data];
            }

            $this->queries[] = [
                'path' => $path,
                'method' => $method,
                'data' => $data,
                'executionMS' => $executionMS,
                'engineMS' => $engineTime,
                'connection' => $connection,
                'queryString' => $query,
                'itemCount' => $itemCount,
                'backtrace' => $e->getTraceAsString(),
            ];
        }

        if (null !== $this->logger) {
            $message = sprintf('%s (%s) %0.2f ms', $path, $method, $executionMS);
            $this->logger->info($message, (array) $data);
        }
    }

    /**
     * Returns the number of queries that have been logged.
     *
     * @return int The number of queries logged
     */
    public function getNbQueries()
    {
        return count($this->queries);
    }

    /**
     * Returns a human-readable array of queries logged.
     *
     * @return array An array of queries
     */
    public function getQueries()
    {
        return $this->queries;
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = [])
    {
        return $this->logger->log($level, $message, $context);
    }

    public function reset()
    {
        $this->queries = [];
    }
}


<?php

namespace FOS\ElasticaBundle\Persister;

use FOS\ElasticaBundle\Persister\Event\Events;
use FOS\ElasticaBundle\Persister\Event\OnExceptionEvent;
use FOS\ElasticaBundle\Persister\Event\PostInsertObjectsEvent;
use FOS\ElasticaBundle\Persister\Event\PostPersistEvent;
use FOS\ElasticaBundle\Persister\Event\PreFetchObjectsEvent;
use FOS\ElasticaBundle\Persister\Event\PreInsertObjectsEvent;
use FOS\ElasticaBundle\Persister\Event\PrePersistEvent;
use FOS\ElasticaBundle\Provider\PagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class InPlacePagerPersister implements PagerPersisterInterface
{
    const NAME = 'in_place';

    /**
     * @var PersisterRegistry
     */
    private $registry;

    /**
     * @var EventDispatcherInterface
     */
    private $dispatcher;

    /**
     * @param PersisterRegistry $registry
     * @param EventDispatcherInterface $dispatcher
     */
    public function __construct(PersisterRegistry $registry, EventDispatcherInterface $dispatcher)
    {
        $this->registry = $registry;
        $this->dispatcher = $dispatcher;
    }

    /**
     * {@inheritdoc}
     */
    public function insert(PagerInterface $pager, array $options = array())
    {
        $pager->setMaxPerPage(empty($options['max_per_page']) ? 100 : $options['max_per_page']);

        $options = array_replace([
            'max_per_page' => $pager->getMaxPerPage(),
            'first_page' => $pager->getCurrentPage(),
            'last_page' => $pager->getNbPages(),
        ], $options);

        $pager->setCurrentPage($options['first_page']);

        $objectPersister = $this->registry->getPersister($options['indexName'], $options['typeName']);

        try {
            $event = new PrePersistEvent($pager, $objectPersister, $options);
            $this->dispatcher->dispatch(Events::PRE_PERSIST, $event);
            $pager = $event->getPager();
            $options = $event->getOptions();

            $lastPage = min($options['last_page'], $pager->getNbPages());
            $page = $pager->getCurrentPage();
            do {
                $pager->setCurrentPage($page);

                $this->insertPage($page, $pager, $objectPersister, $options);

                $page++;
            } while ($page <= $lastPage);
        } finally {
            $event = new PostPersistEvent($pager, $objectPersister, $options);
            $this->dispatcher->dispatch(Events::POST_PERSIST, $event);
        }
    }

    /**
     * @param int $page
     * @param PagerInterface $pager
     * @param ObjectPersisterInterface $objectPersister
     * @param array $options
     *
     * @throws \Exception
     */
    private function insertPage($page, PagerInterface $pager, ObjectPersisterInterface $objectPersister, array $options = array())
    {
        $pager->setCurrentPage($page);

        $event = new PreFetchObjectsEvent($pager, $objectPersister, $options);
        $this->dispatcher->dispatch(Events::PRE_FETCH_OBJECTS, $event);
        $pager = $event->getPager();
        $options = $event->getOptions();

        $objects = $pager->getCurrentPageResults();

        if ($objects instanceof \Traversable) {
            $objects = iterator_to_array($objects);
        }

        $event = new PreInsertObjectsEvent($pager, $objectPersister, $objects, $options);
        $this->dispatcher->dispatch(Events::PRE_INSERT_OBJECTS, $event);
        $pager = $event->getPager();
        $options = $event->getOptions();
        $objects = $event->getObjects();

        try {
            if (!empty($objects)) {
                $objectPersister->insertMany($objects);
            }

            $event = new PostInsertObjectsEvent($pager, $objectPersister, $objects, $options);
            $this->dispatcher->dispatch(Events::POST_INSERT_OBJECTS, $event);
        } catch (\Exception $e) {
            $event = new OnExceptionEvent($pager, $objectPersister, $e, $objects, $options);
            $this->dispatcher->dispatch(Events::ON_EXCEPTION, $event);

            if ($event->isIgnored()) {
                $event = new PostInsertObjectsEvent($pager, $objectPersister, $objects, $options);
                $this->dispatcher->dispatch(Events::POST_INSERT_OBJECTS, $event);
            } else {
                $e = $event->getException();

                throw $e;
            }
        }
    }

}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\DependencyInjection\Compiler;

use FOS\ElasticaBundle\Provider\PagerProviderInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RegisterPagerProvidersPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('fos_elastica.pager_provider_registry')) {
            return;
        }

        $defaultIndex = $container->getParameter('fos_elastica.default_index');
        $registry = $container->getDefinition('fos_elastica.pager_provider_registry');

        $registeredProviders = [];
        foreach ($container->findTaggedServiceIds('fos_elastica.pager_provider', true) as $id => $attributes) {
            foreach ($attributes as $attribute) {
                if (!isset($attribute['type'])) {
                    throw new \InvalidArgumentException(sprintf('Elastica provider "%s" must specify the "type" attribute.', $id));
                }

                $index = isset($attribute['index']) ? $attribute['index'] : $defaultIndex;
                $type = $attribute['type'];

                if (isset($registeredProviders[$index][$type])) {
                    throw new \InvalidArgumentException(sprintf(
                        'Cannot register provider "%s". The provider "%s" has been registered for same index "%s" and type "%s"',
                        $id,
                        $registeredProviders[$index][$type],
                        $index,
                        $type
                    ));
                }

                $providerDef = $container->getDefinition($id);
                if (!$providerDef->getFactory() && $providerDef->getClass()) {
                    // You are on your own if you use a factory to create a provider.
                    // It would fail in runtime if the factory does not return a proper provider.
                    $this->assertClassImplementsPagerProviderInterface($id, $container->getParameterBag()->resolveValue($providerDef->getClass()));
                }

                if (!$providerDef->isPublic()) {
                    throw new \InvalidArgumentException(sprintf('Elastica persister "%s" must be a public service', $id));
                }

                $registeredProviders[$index][$type] = $id;
            }
        }

        $registry->replaceArgument(0, $registeredProviders);
    }

    /**
     * @param $providerId
     * @param $providerClass
     *
     * @throws \InvalidArgumentException if provider service does not implement PagerProviderInterface
     */
    private function assertClassImplementsPagerProviderInterface($providerId, $providerClass)
    {
        $rc = new \ReflectionClass($providerClass);

        if (!$rc->implementsInterface(PagerProviderInterface::class)) {
            throw new \InvalidArgumentException(sprintf(
                'Elastica provider "%s" with class "%s" must implement "%s".',
                $providerId,
                $providerClass,
                PagerProviderInterface::class
            ));
        }
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Persister;

use Elastica\Document;
use Elastica\Type;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;

/**
 * Inserts, replaces and deletes single objects in an elastica type, making use
 * of elastica's serializer support to convert objects in to elastica documents.
 * Accepts domain model objects and passes them directly to elastica.
 *
 * @author Lea Haensenberber <lea.haensenberger@gmail.com>
 */
class ObjectSerializerPersister extends ObjectPersister
{
    protected $serializer;

    /**
     * @param Type                                $type
     * @param ModelToElasticaTransformerInterface $transformer
     * @param string                              $objectClass
     * @param callable                            $serializer
     */
    public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer, array $options = [])
    {
        parent::__construct($type, $transformer, $objectClass, [], $options);

        $this->serializer = $serializer;
    }

    /**
     * Transforms an object to an elastica document
     * with just the identifier set.
     *
     * @param object $object
     *
     * @return Document the elastica document
     */
    public function transformToElasticaDocument($object)
    {
        $document = $this->transformer->transform($object, []);

        $data = call_user_func($this->serializer, $object);
        $document->setData($data);

        return $document;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Paginator;

use Elastica\Query;
use Elastica\ResultSet;
use Elastica\SearchableInterface;
use InvalidArgumentException;

/**
 * Allows pagination of Elastica\Query. Does not map results.
 */
class RawPaginatorAdapter implements PaginatorAdapterInterface
{
    /**
     * @var SearchableInterface the object to search in
     */
    private $searchable;

    /**
     * @var Query the query to search
     */
    private $query;

    /**
     * @var array search options
     */
    private $options;

    /**
     * @var int the number of hits
     */
    private $totalHits;

    /**
     * @var array for the aggregations
     */
    private $aggregations;

    /**
     * @var array for the suggesters
     */
    private $suggests;

    /**
     * @var float
     */
    private $maxScore;

    /**
     * @see PaginatorAdapterInterface::__construct
     *
     * @param SearchableInterface $searchable the object to search in
     * @param Query               $query      the query to search
     * @param array               $options
     */
    public function __construct(SearchableInterface $searchable, Query $query, array $options = [])
    {
        $this->searchable = $searchable;
        $this->query = $query;
        $this->options = $options;
    }

    /**
     * {@inheritdoc}
     */
    public function getResults($offset, $itemCountPerPage)
    {
        return new RawPartialResults($this->getElasticaResults($offset, $itemCountPerPage));
    }

    /**
     * Returns the number of results.
     *
     * If genuineTotal is provided as true, total hits is returned from the
     * hits.total value from the search results instead of just returning
     * the requested size.
     *
     * {@inheritdoc}
     */
    public function getTotalHits($genuineTotal = false)
    {
        if (!isset($this->totalHits)) {
            $this->totalHits = $this->searchable->count($this->query);
        }

        return $this->query->hasParam('size') && !$genuineTotal
            ? min($this->totalHits, (int) $this->query->getParam('size'))
            : $this->totalHits;
    }

    /**
     * {@inheritdoc}
     */
    public function getAggregations()
    {
        if (!isset($this->aggregations)) {
            $this->aggregations = $this->searchable->search($this->query)->getAggregations();
        }

        return $this->aggregations;
    }

    /**
     * {@inheritdoc}
     */
    public function getSuggests()
    {
        if (!isset($this->suggests)) {
            $this->suggests = $this->searchable->search($this->query)->getSuggests();
        }

        return $this->suggests;
    }

    /**
     * @return float
     */
    public function getMaxScore()
    {
        if (!isset($this->maxScore)) {
            $this->maxScore = $this->searchable->search($this->query)->getMaxScore();
        }

        return $this->maxScore;
    }

    /**
     * Returns the Query.
     *
     * @return Query the search query
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * Returns the paginated results.
     *
     * @param int $offset
     * @param int $itemCountPerPage
     *
     * @throws \InvalidArgumentException
     *
     * @return ResultSet
     */
    protected function getElasticaResults($offset, $itemCountPerPage)
    {
        $offset = (int) $offset;
        $itemCountPerPage = (int) $itemCountPerPage;
        $size = $this->query->hasParam('size')
            ? (int) $this->query->getParam('size')
            : null;

        if (null !== $size && $size < $offset + $itemCountPerPage) {
            $itemCountPerPage = $size - $offset;
        }

        if ($itemCountPerPage < 1) {
            throw new InvalidArgumentException('$itemCountPerPage must be greater than zero');
        }

        $query = clone $this->query;
        $query->setFrom($offset);
        $query->setSize($itemCountPerPage);

        $resultSet = $this->searchable->search($query, $this->options);
        $this->totalHits = $resultSet->getTotalHits();
        $this->aggregations = $resultSet->getAggregations();
        $this->suggests = $resultSet->getSuggests();
        $this->maxScore = $resultSet->getMaxScore();

        return $resultSet;
    }
}


<?php
namespace FOS\ElasticaBundle\Index;

use FOS\ElasticaBundle\Configuration\ManagerInterface;
use Elastica\Client;
use Elastica\Request;
use FOS\ElasticaBundle\Configuration\IndexTemplateConfig;

/**
 * Class Template resetter
 *
 * @author Dmitry Balabka <dmitry.balabka@intexsys.lv>
 */
class TemplateResetter implements ResetterInterface
{
    /***
     * @var ManagerInterface
     */
    private $configManager;

    /**
     * @var MappingBuilder
     */
    private $mappingBuilder;

    /**
     * @var Client
     */
    private $client;

    /**
     * Index template manager
     *
     * @var IndexTemplateManager
     */
    private $indexTemplateManager;

    public function __construct(
        ManagerInterface $configManager,
        MappingBuilder $mappingBuilder,
        Client $client,
        IndexTemplateManager $indexTemplateManager
    ) {
        $this->configManager = $configManager;
        $this->mappingBuilder = $mappingBuilder;
        $this->client = $client;
        $this->indexTemplateManager = $indexTemplateManager;
    }

    public function resetAllIndexes($deleteIndexes = false)
    {
        foreach ($this->configManager->getIndexNames() as $name) {
            $this->resetIndex($name, $deleteIndexes);
        }
    }

    public function resetIndex($indexName, $deleteIndexes = false)
    {
        $indexTemplateConfig = $this->configManager->getIndexConfiguration($indexName);
        if (!$indexTemplateConfig instanceof IndexTemplateConfig) {
            throw new \RuntimeException(
                \sprintf('Incorrect index configuration object. Expecting IndexTemplateConfig, but got: %s ', \get_class($indexTemplateConfig))
            );
        }
        $indexTemplate = $this->indexTemplateManager->getIndexTemplate($indexName);

        $mapping = $this->mappingBuilder->buildIndexTemplateMapping($indexTemplateConfig);
        if ($deleteIndexes) {
            $this->deleteTemplateIndexes($indexTemplateConfig);
        }
        $indexTemplate->create($mapping);
    }

    /**
     * Delete all template indexes
     *
     * @param IndexTemplateConfig $template
     */
    public function deleteTemplateIndexes(IndexTemplateConfig $template)
    {
        $this->client->request($template->getTemplate() . '/', Request::DELETE);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Index;

use FOS\ElasticaBundle\Elastica\Index;

class IndexManager
{
    /**
     * @var Index
     */
    private $defaultIndex;

    /**
     * @var array
     */
    private $indexes;

    /**
     * @param array $indexes
     * @param Index $defaultIndex
     */
    public function __construct(array $indexes, Index $defaultIndex)
    {
        $this->defaultIndex = $defaultIndex;
        $this->indexes = $indexes;
    }

    /**
     * Gets all registered indexes.
     *
     * @return array
     */
    public function getAllIndexes()
    {
        return $this->indexes;
    }

    /**
     * Gets an index by its name.
     *
     * @param string $name Index to return, or the default index if null
     *
     * @return Index
     *
     * @throws \InvalidArgumentException if no index exists for the given name
     */
    public function getIndex($name = null)
    {
        if (null === $name) {
            return $this->defaultIndex;
        }

        if (!isset($this->indexes[$name])) {
            throw new \InvalidArgumentException(sprintf('The index "%s" does not exist', $name));
        }

        return $this->indexes[$name];
    }

    /**
     * Gets the default index.
     *
     * @return Index
     */
    public function getDefaultIndex()
    {
        return $this->defaultIndex;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Persister;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class PersisterRegistry implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    /**
     * @var array
     */
    private $persisters = [];

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

    /**
     * Gets the persister for an index and type.
     *
     * @param string $index
     * @param string $type
     *
     * @return ObjectPersisterInterface
     *
     * @throws \InvalidArgumentException if no persister was registered for the index and type
     */
    public function getPersister($index, $type)
    {
        if (!isset($this->persisters[$index][$type])) {
            throw new \InvalidArgumentException(sprintf('No persister was registered for index "%s" and type "%s".', $index, $type));
        }

        return $this->container->get($this->persisters[$index][$type]);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Command;

use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Event\IndexPopulateEvent;
use FOS\ElasticaBundle\Event\TypePopulateEvent;
use FOS\ElasticaBundle\Index\IndexManager;
use FOS\ElasticaBundle\Index\Resetter;
use FOS\ElasticaBundle\Persister\Event\Events;
use FOS\ElasticaBundle\Persister\Event\OnExceptionEvent;
use FOS\ElasticaBundle\Persister\Event\PostAsyncInsertObjectsEvent;
use FOS\ElasticaBundle\Persister\Event\PostInsertObjectsEvent;
use FOS\ElasticaBundle\Persister\InPlacePagerPersister;
use FOS\ElasticaBundle\Persister\PagerPersisterInterface;
use FOS\ElasticaBundle\Persister\PagerPersisterRegistry;
use FOS\ElasticaBundle\Provider\PagerProviderRegistry;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Populate the search index.
 */
class PopulateCommand extends Command
{
    protected static $defaultName = 'fos:elastica:populate';

    /**
     * @var EventDispatcherInterface
     */
    private $dispatcher;

    /**
     * @var IndexManager
     */
    private $indexManager;

    /**
     * @var PagerProviderRegistry
     */
    private $pagerProviderRegistry;

    /**
     * @var PagerPersisterRegistry
     */
    private $pagerPersisterRegistry;

    /**
     * @var PagerPersisterInterface
     */
    private $pagerPersister;

    /**
     * @var Resetter
     */
    private $resetter;

    public function __construct(
        EventDispatcherInterface $dispatcher,
        IndexManager $indexManager,
        PagerProviderRegistry $pagerProviderRegistry,
        PagerPersisterRegistry $pagerPersisterRegistry,
        Resetter $resetter
    ) {
        parent::__construct();

        $this->dispatcher = $dispatcher;
        $this->indexManager = $indexManager;
        $this->pagerProviderRegistry = $pagerProviderRegistry;
        $this->pagerPersisterRegistry = $pagerPersisterRegistry;
        $this->resetter = $resetter;
    }

    protected function configure()
    {
        $this
            ->setName('fos:elastica:populate')
            ->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to repopulate')
            ->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type to repopulate')
            ->addOption('no-reset', null, InputOption::VALUE_NONE, 'Do not reset index before populating')
            ->addOption('no-delete', null, InputOption::VALUE_NONE, 'Do not delete index after populate')
            ->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Sleep time between persisting iterations (microseconds)', 0)
            ->addOption('ignore-errors', null, InputOption::VALUE_NONE, 'Do not stop on errors')
            ->addOption('no-overwrite-format', null, InputOption::VALUE_NONE, 'Prevent this command from overwriting ProgressBar\'s formats')

            ->addOption('first-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page to start population from. Including the given page.', 1)
            ->addOption('last-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page to end population on. Including the given page.', null)
            ->addOption('max-per-page', null, InputOption::VALUE_REQUIRED, 'The pager\'s page size', 100)
            ->addOption('pager-persister', null, InputOption::VALUE_REQUIRED, 'The pager persister to be used to populate the index', InPlacePagerPersister::NAME)

            ->setDescription('Populates search indexes from providers')
        ;
    }

    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        $this->pagerPersister = $this->pagerPersisterRegistry->getPagerPersister($input->getOption('pager-persister'));

        if (!$input->getOption('no-overwrite-format')) {
            ProgressBar::setFormatDefinition('normal', " %current%/%max% [%bar%] %percent:3s%%\n%message%");
            ProgressBar::setFormatDefinition('verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%\n%message%");
            ProgressBar::setFormatDefinition('very_verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n%message%");
            ProgressBar::setFormatDefinition('debug', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%\n%message%");
        }
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $index = $input->getOption('index');
        $type = $input->getOption('type');
        $reset = !$input->getOption('no-reset');
        $delete = !$input->getOption('no-delete');

        $options = [
            'delete' => $delete,
            'reset' => $reset,
            'ignore_errors' => $input->getOption('ignore-errors'),
            'sleep' => $input->getOption('sleep'),
            'first_page' => $input->getOption('first-page'),
            'max_per_page' => $input->getOption('max-per-page'),
        ];

        if ($input->getOption('last-page')) {
            $options['last_page'] = $input->getOption('last-page');
        }

        if ($input->isInteractive() && $reset && 1 < $options['first_page']) {
            /** @var QuestionHelper $dialog */
            $dialog = $this->getHelperSet()->get('question');
            if (!$dialog->ask($input, $output, new Question('<question>You chose to reset the index and start indexing with an offset. Do you really want to do that?</question>'))) {
                return;
            }
        }

        if (null === $index && null !== $type) {
            throw new \InvalidArgumentException('Cannot specify type option without an index.');
        }

        if (null !== $index) {
            if (null !== $type) {
                $this->populateIndexType($output, $index, $type, $reset, $options);
            } else {
                $this->populateIndex($output, $index, $reset, $options);
            }
        } else {
            $indexes = array_keys($this->indexManager->getAllIndexes());

            foreach ($indexes as $index) {
                $this->populateIndex($output, $index, $reset, $options);
            }
        }
    }

    /**
     * Recreates an index, populates its types, and refreshes the index.
     *
     * @param OutputInterface $output
     * @param string          $index
     * @param bool            $reset
     * @param array           $options
     */
    private function populateIndex(OutputInterface $output, $index, $reset, $options)
    {
        $event = new IndexPopulateEvent($index, $reset, $options);
        $this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event);

        if ($event->isReset()) {
            $output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
            $this->resetter->resetIndex($index, true);
        }

        $types = array_keys($this->pagerProviderRegistry->getIndexProviders($index));
        foreach ($types as $type) {
            $this->populateIndexType($output, $index, $type, false, $event->getOptions());
        }

        $this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);

        $this->refreshIndex($output, $index);
    }

    /**
     * Deletes/remaps an index type, populates it, and refreshes the index.
     *
     * @param OutputInterface $output
     * @param string          $index
     * @param string          $type
     * @param bool            $reset
     * @param array           $options
     */
    private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options)
    {
        $event = new TypePopulateEvent($index, $type, $reset, $options);
        $this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event);

        if ($event->isReset()) {
            $output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
            $this->resetter->resetIndexType($index, $type);
        }

        $offset = 1 < $options['first_page'] ? ($options['first_page'] - 1) * $options['max_per_page'] : 0;
        $loggerClosure = ProgressClosureBuilder::build($output, 'Populating', $index, $type, $offset);

        $this->dispatcher->addListener(
            Events::ON_EXCEPTION,
            function(OnExceptionEvent $event) use ($loggerClosure) {
                $loggerClosure(
                    count($event->getObjects()),
                    $event->getPager()->getNbResults(),
                    sprintf('<error>%s</error>', $event->getException()->getMessage())
                );
            }
        );

        $this->dispatcher->addListener(
            Events::POST_INSERT_OBJECTS,
            function(PostInsertObjectsEvent $event) use ($loggerClosure) {
                $loggerClosure(count($event->getObjects()), $event->getPager()->getNbResults());
            }
        );

        $this->dispatcher->addListener(
            Events::POST_ASYNC_INSERT_OBJECTS,
            function(PostAsyncInsertObjectsEvent $event) use ($loggerClosure) {
                $loggerClosure($event->getObjectsCount(), $event->getPager()->getNbResults(), $event->getErrorMessage());
            }
        );

        if ($options['ignore_errors']) {
            $this->dispatcher->addListener(Events::ON_EXCEPTION, function(OnExceptionEvent $event) {
                if ($event->getException() instanceof BulkResponseException) {
                    $event->setIgnore(true);
                }
            });
        }

        $provider = $this->pagerProviderRegistry->getProvider($index, $type);

        $pager = $provider->provide($options);

        $options['indexName'] = $index;
        $options['typeName'] = $type;

        $this->pagerPersister->insert($pager, $options);

        $this->dispatcher->dispatch(TypePopulateEvent::POST_TYPE_POPULATE, $event);

        $this->refreshIndex($output, $index);
    }

    /**
     * Refreshes an index.
     *
     * @param OutputInterface $output
     * @param string          $index
     */
    private function refreshIndex(OutputInterface $output, $index)
    {
        $output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
        $this->indexManager->getIndex($index)->refresh();
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Transformer;

use Elastica\Document;
use FOS\ElasticaBundle\Event\TransformEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

/**
 * Maps Elastica documents with Doctrine objects
 * This mapper assumes an exact match between
 * elastica documents ids and doctrine object ids.
 */
class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterface
{
    /**
     * @var EventDispatcherInterface
     */
    protected $dispatcher;

    /**
     * Optional parameters.
     *
     * @var array
     */
    protected $options = [
        'identifier' => 'id',
        'index' => '',
    ];

    /**
     * PropertyAccessor instance.
     *
     * @var PropertyAccessorInterface
     */
    protected $propertyAccessor;

    /**
     * Instanciates a new Mapper.
     *
     * @param array                    $options
     * @param EventDispatcherInterface $dispatcher
     */
    public function __construct(array $options = [], EventDispatcherInterface $dispatcher = null)
    {
        $this->options = array_merge($this->options, $options);
        $this->dispatcher = $dispatcher;
    }

    /**
     * Set the PropertyAccessor.
     *
     * @param PropertyAccessorInterface $propertyAccessor
     */
    public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
    {
        $this->propertyAccessor = $propertyAccessor;
    }

    /**
     * Transforms an object into an elastica object having the required keys.
     *
     * @param object $object the object to convert
     * @param array  $fields the keys we want to have in the returned array
     *
     * @return Document
     **/
    public function transform($object, array $fields)
    {
        $identifier = $this->propertyAccessor->getValue($object, $this->options['identifier']);
        if ($identifier && !is_scalar($identifier)) {
            $identifier = (string) $identifier;
        }

        return $this->transformObjectToDocument($object, $fields, $identifier);
    }

    /**
     * transform a nested document or an object property into an array of ElasticaDocument.
     *
     * @param array|\Traversable|\ArrayAccess $objects the object to convert
     * @param array                           $fields  the keys we want to have in the returned array
     *
     * @return array
     */
    protected function transformNested($objects, array $fields)
    {
        if (is_array($objects) || $objects instanceof \Traversable || $objects instanceof \ArrayAccess) {
            $documents = [];
            foreach ($objects as $object) {
                $document = $this->transformObjectToDocument($object, $fields);
                $documents[] = $document->getData();
            }

            return $documents;
        } elseif (null !== $objects) {
            $document = $this->transformObjectToDocument($objects, $fields);

            return $document->getData();
        }

        return [];
    }

    /**
     * Attempts to convert any type to a string or an array of strings.
     *
     * @param mixed $value
     *
     * @return string|array
     */
    protected function normalizeValue($value)
    {
        $normalizeValue = function (&$v) {
            if ($v instanceof \DateTimeInterface) {
                $v = $v->format('c');
            } elseif (!is_scalar($v) && !is_null($v)) {
                $v = (string) $v;
            }
        };

        if (is_array($value) || $value instanceof \Traversable || $value instanceof \ArrayAccess) {
            $value = is_array($value) ? $value : iterator_to_array($value, false);
            array_walk_recursive($value, $normalizeValue);
        } else {
            $normalizeValue($value);
        }

        return $value;
    }

    /**
     * Transforms the given object to an elastica document.
     *
     * @param object $object     the object to convert
     * @param array  $fields     the keys we want to have in the returned array
     * @param string $identifier the identifier for the new document
     *
     * @return Document
     */
    protected function transformObjectToDocument($object, array $fields, $identifier = '')
    {
        $document = new Document($identifier, [], '', $this->options['index']);

        if ($this->dispatcher) {
            $event = new TransformEvent($document, $fields, $object);
            $this->dispatcher->dispatch(TransformEvent::PRE_TRANSFORM, $event);

            $document = $event->getDocument();
        }

        foreach ($fields as $key => $mapping) {
            if ('_parent' == $key) {
                $property = (null !== $mapping['property']) ? $mapping['property'] : $mapping['type'];
                $value = $this->propertyAccessor->getValue($object, $property);
                $document->setParent($this->propertyAccessor->getValue($value, $mapping['identifier']));

                continue;
            }

            $path = isset($mapping['property_path']) ?
                $mapping['property_path'] :
                $key;
            if (false === $path) {
                continue;
            }
            $value = $this->propertyAccessor->getValue($object, $path);

            if (isset($mapping['type']) && in_array(
                    $mapping['type'], ['nested', 'object']
                ) && isset($mapping['properties']) && !empty($mapping['properties'])
            ) {
                /* $value is a nested document or object. Transform $value into
                 * an array of documents, respective the mapped properties.
                 */
                $document->set($key, $this->transformNested($value, $mapping['properties']));

                continue;
            }

            if (isset($mapping['type']) && 'attachment' == $mapping['type']) {
                // $value is an attachment. Add it to the document.
                if ($value instanceof \SplFileInfo) {
                    $document->addFile($key, $value->getPathName());
                } else {
                    $document->addFileContent($key, $value);
                }

                continue;
            }

            $document->set($key, $this->normalizeValue($value));
        }

        if ($this->dispatcher) {
            $event = new TransformEvent($document, $fields, $object);
            $this->dispatcher->dispatch(TransformEvent::POST_TRANSFORM, $event);

            $document = $event->getDocument();
        }

        return $document;
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Doctrine\ORM;

use Doctrine\ORM\Query;
use FOS\ElasticaBundle\Doctrine\AbstractElasticaToModelTransformer;

/**
 * Maps Elastica documents with Doctrine objects
 * This mapper assumes an exact match between
 * elastica documents ids and doctrine object ids.
 */
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
    const ENTITY_ALIAS = 'o';

    /**
     * Fetch objects for theses identifier values.
     *
     * @param array $identifierValues ids values
     * @param bool  $hydrate          whether or not to hydrate the objects, false returns arrays
     *
     * @return array of objects or arrays
     */
    protected function findByIdentifiers(array $identifierValues, $hydrate)
    {
        if (empty($identifierValues)) {
            return [];
        }
        $hydrationMode = $hydrate ? Query::HYDRATE_OBJECT : Query::HYDRATE_ARRAY;

        $qb = $this->getEntityQueryBuilder();
        $qb->andWhere($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values'))
            ->setParameter('values', $identifierValues);

        $query = $qb->getQuery();

        foreach ($this->options['hints'] as $hint) {
            $query->setHint($hint['name'], $hint['value']);
        }

        return $query->setHydrationMode($hydrationMode)->execute();
    }

    /**
     * Retrieves a query builder to be used for querying by identifiers.
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function getEntityQueryBuilder()
    {
        $repository = $this->registry
            ->getManagerForClass($this->objectClass)
            ->getRepository($this->objectClass);

        return $repository->{$this->options['query_builder_method']}(static::ENTITY_ALIAS);
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\DependencyInjection\Compiler;

use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RegisterPersistersPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('fos_elastica.persister_registry')) {
            return;
        }

        $defaultIndex = $container->getParameter('fos_elastica.default_index');
        $registry = $container->getDefinition('fos_elastica.persister_registry');

        $registeredPersisters = [];
        foreach ($container->findTaggedServiceIds('fos_elastica.persister', true) as $id => $attributes) {
            foreach ($attributes as $attribute) {
                if (!isset($attribute['type'])) {
                    throw new \InvalidArgumentException(sprintf('Elastica persister "%s" must specify the "type" attribute.', $id));
                }

                $index = isset($attribute['index']) ? $attribute['index'] : $defaultIndex;
                $type = $attribute['type'];

                if (isset($registeredPersisters[$index][$type])) {
                    throw new \InvalidArgumentException(sprintf(
                        'Cannot register persister "%s". The persister "%s" has been registered for same index "%s" and type "%s"',
                        $id,
                        $registeredPersisters[$index][$type],
                        $index,
                        $type
                    ));
                }

                $persisterDef = $container->getDefinition($id);
                if (!$persisterDef->getFactory() && $persisterDef->getClass()) {
                    // You are on your own if you use a factory to create a persister.
                    // It would fail in runtime if the factory does not return a proper persister.
                    $this->assertClassImplementsPersisterInterface($id, $container->getParameterBag()->resolveValue($persisterDef->getClass()));
                }

                if (!$persisterDef->isPublic()) {
                    throw new \InvalidArgumentException(sprintf('Elastica persister "%s" must be a public service', $id));
                }

                $registeredPersisters[$index][$type] = $id;
            }
        }

        $registry->replaceArgument(0, $registeredPersisters);
    }

    /**
     * @param $persisterId
     * @param $persisterClass
     *
     * @throws \InvalidArgumentException if persister service does not implement ObjectPersisterInterface
     */
    private function assertClassImplementsPersisterInterface($persisterId, $persisterClass)
    {
        $rc = new \ReflectionClass($persisterClass);

        if (!$rc->implementsInterface(ObjectPersisterInterface::class)) {
            throw new \InvalidArgumentException(sprintf(
                'Elastica persister "%s" with class "%s" must implement "%s".',
                $persisterId,
                $persisterClass,
                ObjectPersisterInterface::class
            ));
        }
    }
}


<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FOS\ElasticaBundle\Transformer;

use FOS\ElasticaBundle\HybridResult;

/**
 * Holds a collection of transformers for an index wide transformation.
 *
 * @author Tim Nagel <tim@nagel.com.au>
 */
class ElasticaToModelTransformerCollection implements ElasticaToModelTransformerInterface
{
    /**
     * @var ElasticaToModelTransformerInterface[]
     */
    protected $transformers = [];

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

    /**
     * {@inheritdoc}
     */
    public function getObjectClass()
    {
        return array_map(function (ElasticaToModelTransformerInterface $transformer) {
            return $transformer->getObjectClass();
        }, $this->transformers);
    }

    /**
     * {@inheritdoc}
     */
    public function getIdentifierField()
    {
        return array_map(function (ElasticaToModelTransformerInterface $transformer) {
            return $transformer->getIdentifierField();
        }, $this->transformers);
    }

    /**
     * {@inheritdoc}
     */
    public function transform(array $elasticaObjects)
    {
        $sorted = [];
        foreach ($elasticaObjects as $object) {
            $sorted[$object->getType()][] = $object;
        }

        $transformed = [];
        foreach ($sorted as $type => $objects) {
            $transformedObjects = $this->transformers[$type]->transform($objects);
            $identifierGetter = 'get'.ucfirst($this->transformers[$type]->getIdentifierField());
            $transformed[$type] = array_combine(
                array_map(
                    function ($o) use ($identifierGetter) {
                        return $o->$identifierGetter();
                    },
                    $transformedObjects
                ),
                $transformedObjects
            );
        }

        $result = [];
        foreach ($elasticaObjects as $object) {
            if (array_key_exists((string) $object->getId(), $transformed[$object->getType()])) {
                $result[] = $transformed[$object->getType()][(string) $object->getId()];
            }
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function hybridTransform(array $elasticaObjects)
    {
        $objects = $this->transform($elasticaObjects);

        $result = [];
        for ($i = 0, $j = count($elasticaObjects); $i < $j; ++$i) {
            if (!isset($objects[$i])) {
                continue;
            }
            $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
        }

        return $result;
    }
}