vendor/handcraftedinthealps/elasticsearch-bundle/Mapping/MetadataCollector.php line 71

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the ONGR package.
  4.  *
  5.  * (c) NFQ Technologies UAB <info@nfq.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace ONGR\ElasticsearchBundle\Mapping;
  11. use Doctrine\Common\Cache\CacheProvider;
  12. use ONGR\ElasticsearchBundle\Exception\DocumentParserException;
  13. use ONGR\ElasticsearchBundle\Exception\MissingDocumentAnnotationException;
  14. /**
  15.  * DocumentParser wrapper for getting bundle documents mapping.
  16.  */
  17. class MetadataCollector
  18. {
  19.     /**
  20.      * @var DocumentFinder
  21.      */
  22.     private $finder;
  23.     /**
  24.      * @var DocumentParser
  25.      */
  26.     private $parser;
  27.     /**
  28.      * @var CacheProvider
  29.      */
  30.     private $cache null;
  31.     /**
  32.      * @var bool
  33.      */
  34.     private $enableCache false;
  35.     /**
  36.      * @param DocumentFinder $finder For finding documents.
  37.      * @param DocumentParser $parser For reading document annotations.
  38.      * @param CacheProvider  $cache  Cache provider to store the meta data for later use.
  39.      */
  40.     public function __construct($finder$parser$cache null)
  41.     {
  42.         $this->finder $finder;
  43.         $this->parser $parser;
  44.         $this->cache $cache;
  45.     }
  46.     /**
  47.      * Enables metadata caching.
  48.      *
  49.      * @param bool $enableCache
  50.      */
  51.     public function setEnableCache($enableCache)
  52.     {
  53.         $this->enableCache $enableCache;
  54.     }
  55.     /**
  56.      * Fetches bundles mapping from documents.
  57.      *
  58.      * @param string[] $bundles Elasticsearch manager config. You can get bundles list from 'mappings' node.
  59.      * @return array
  60.      */
  61.     public function getMappings(array $bundles)
  62.     {
  63.         $output = [];
  64.         foreach ($bundles as $name => $bundleConfig) {
  65.             // Backward compatibility hack for support.
  66.             if (!is_array($bundleConfig)) {
  67.                 $name $bundleConfig;
  68.                 $bundleConfig = [];
  69.             }
  70.             $mappings $this->getBundleMapping($name$bundleConfig);
  71.             $alreadyDefinedTypes array_intersect_key($mappings$output);
  72.             if (count($alreadyDefinedTypes)) {
  73.                 throw new \LogicException(
  74.                     implode(','array_keys($alreadyDefinedTypes)) .
  75.                     ' type(s) already defined in other document, you can use the same ' .
  76.                     'type only once in a manager definition.'
  77.                 );
  78.             }
  79.             $output array_merge($output$mappings);
  80.         }
  81.         return $output;
  82.     }
  83.     /**
  84.      * Searches for documents in the bundle and tries to read them.
  85.      *
  86.      * @param string $name
  87.      * @param array $config Bundle configuration
  88.      *
  89.      * @return array Empty array on containing zero documents.
  90.      */
  91.     public function getBundleMapping($name$config = [])
  92.     {
  93.         if (!is_string($name)) {
  94.             throw new \LogicException('getBundleMapping() in the Metadata collector expects a string argument only!');
  95.         }
  96.         $cacheName =  'ongr.metadata.mapping.' md5($name.serialize($config));
  97.         $this->enableCache && $mappings $this->cache->fetch($cacheName);
  98.         if (isset($mappings) && false !== $mappings) {
  99.             return $mappings;
  100.         }
  101.         $mappings = [];
  102.         $documentDir = isset($config['document_dir']) ? $config['document_dir'] : $this->finder->getDocumentDir();
  103.         // Handle the case when single document mapping requested
  104.         // Usage od ":" in name is deprecated. This if is only for BC.
  105.         if (strpos($name':') !== false) {
  106.             list($bundle$documentClass) = explode(':'$name);
  107.             $documents $this->finder->getBundleDocumentClasses($bundle);
  108.             $documents in_array($documentClass$documents) ? [$documentClass] : [];
  109.         } else {
  110.             $documents $this->finder->getBundleDocumentClasses($name$documentDir);
  111.             $bundle $name;
  112.         }
  113.         $bundleNamespace $this->finder->getBundleClass($bundle);
  114.         $bundleNamespace substr($bundleNamespace0strrpos($bundleNamespace'\\'));
  115.         if (!count($documents)) {
  116.             return [];
  117.         }
  118.         // Loop through documents found in bundle.
  119.         foreach ($documents as $document) {
  120.             $documentReflection = new \ReflectionClass(
  121.                 $bundleNamespace .
  122.                 '\\' str_replace('/''\\'$documentDir) .
  123.                 '\\' $document
  124.             );
  125.             try {
  126.                 $documentMapping $this->getDocumentReflectionMapping($documentReflection);
  127.                 if (!$documentMapping) {
  128.                     continue;
  129.                 }
  130.             } catch (MissingDocumentAnnotationException $exception) {
  131.                 // Not a document, just ignore
  132.                 continue;
  133.             }
  134.             if (!array_key_exists($documentMapping['type'], $mappings)) {
  135.                 $documentMapping['bundle'] = $bundle;
  136.                 $mappings array_merge($mappings, [$documentMapping['type'] => $documentMapping]);
  137.             } else {
  138.                 throw new \LogicException(
  139.                     $bundle ' has 2 same type names defined in the documents. ' .
  140.                     'Type names must be unique!'
  141.                 );
  142.             }
  143.         }
  144.         $this->enableCache && $this->cache->save($cacheName$mappings);
  145.         return $mappings;
  146.     }
  147.     /**
  148.      * @param array $manager
  149.      *
  150.      * @return array
  151.      */
  152.     public function getManagerTypes($manager)
  153.     {
  154.         $mapping $this->getMappings($manager['mappings']);
  155.         return array_keys($mapping);
  156.     }
  157.     /**
  158.      * Resolves Elasticsearch type by document class.
  159.      *
  160.      * @param string $className FQCN or string in AppBundle:Document format
  161.      *
  162.      * @return string
  163.      */
  164.     public function getDocumentType($className)
  165.     {
  166.         $mapping $this->getMapping($className);
  167.         return $mapping['type'];
  168.     }
  169.     /**
  170.      * Retrieves prepared mapping to sent to the elasticsearch client.
  171.      *
  172.      * @param array $bundles Manager config.
  173.      *
  174.      * @return array|null
  175.      */
  176.     public function getClientMapping(array $bundles)
  177.     {
  178.         /** @var array $typesMapping Array of filtered mappings for the elasticsearch client*/
  179.         $typesMapping null;
  180.         /** @var array $mappings All mapping info */
  181.         $mappings $this->getMappings($bundles);
  182.         foreach ($mappings as $type => $mapping) {
  183.             if (!empty($mapping['properties'])) {
  184.                 $typesMapping[$type] = array_filter(
  185.                     array_merge(
  186.                         ['properties' => $mapping['properties']],
  187.                         $mapping['fields']
  188.                     ),
  189.                     function ($value) {
  190.                         return (bool)$value || is_bool($value);
  191.                     }
  192.                 );
  193.             }
  194.         }
  195.         return $typesMapping;
  196.     }
  197.     /**
  198.      * Prepares analysis node for Elasticsearch client.
  199.      *
  200.      * @param array $bundles
  201.      * @param array $analysisConfig
  202.      *
  203.      * @return array
  204.      */
  205.     public function getClientAnalysis(array $bundles$analysisConfig = [])
  206.     {
  207.         $cacheName 'ongr.metadata.analysis.'.md5(serialize($bundles));
  208.         $this->enableCache && $typesAnalysis $this->cache->fetch($cacheName);
  209.         if (isset($typesAnalysis) && false !== $typesAnalysis) {
  210.             return $typesAnalysis;
  211.         }
  212.         $typesAnalysis = [
  213.             'analyzer' => [],
  214.             'filter' => [],
  215.             'tokenizer' => [],
  216.             'char_filter' => [],
  217.             'normalizer' => [],
  218.         ];
  219.         /** @var array $mappings All mapping info */
  220.         $mappings $this->getMappings($bundles);
  221.         foreach ($mappings as $type => $metadata) {
  222.             foreach ($metadata['analyzers'] as $analyzerName) {
  223.                 if (isset($analysisConfig['analyzer'][$analyzerName])) {
  224.                     $analyzer $analysisConfig['analyzer'][$analyzerName];
  225.                     $typesAnalysis['analyzer'][$analyzerName] = $analyzer;
  226.                     $typesAnalysis['filter'] = $this->getAnalysisNodeConfiguration(
  227.                         'filter',
  228.                         $analyzer,
  229.                         $analysisConfig,
  230.                         $typesAnalysis['filter']
  231.                     );
  232.                     $typesAnalysis['tokenizer'] = $this->getAnalysisNodeConfiguration(
  233.                         'tokenizer',
  234.                         $analyzer,
  235.                         $analysisConfig,
  236.                         $typesAnalysis['tokenizer']
  237.                     );
  238.                     $typesAnalysis['char_filter'] = $this->getAnalysisNodeConfiguration(
  239.                         'char_filter',
  240.                         $analyzer,
  241.                         $analysisConfig,
  242.                         $typesAnalysis['char_filter']
  243.                     );
  244.                 }
  245.             }
  246.         }
  247.         if (isset($analysisConfig['normalizer'])) {
  248.             $typesAnalysis['normalizer'] = $analysisConfig['normalizer'];
  249.         }
  250.         $this->enableCache && $this->cache->save($cacheName$typesAnalysis);
  251.         return $typesAnalysis;
  252.     }
  253.     /**
  254.      * Prepares analysis node content for Elasticsearch client.
  255.      *
  256.      * @param string $type Node type: filter, tokenizer or char_filter
  257.      * @param array $analyzer Analyzer from which used helpers will be extracted.
  258.      * @param array $analysisConfig Pre configured analyzers container
  259.      * @param array $container Current analysis container where prepared helpers will be appended.
  260.      *
  261.      * @return array
  262.      */
  263.     private function getAnalysisNodeConfiguration($type$analyzer$analysisConfig$container = [])
  264.     {
  265.         if (isset($analyzer[$type])) {
  266.             if (is_array($analyzer[$type])) {
  267.                 foreach ($analyzer[$type] as $filter) {
  268.                     if (isset($analysisConfig[$type][$filter])) {
  269.                         $container[$filter] = $analysisConfig[$type][$filter];
  270.                     }
  271.                 }
  272.             } else {
  273.                 if (isset($analysisConfig[$type][$analyzer[$type]])) {
  274.                     $container[$analyzer[$type]] = $analysisConfig[$type][$analyzer[$type]];
  275.                 }
  276.             }
  277.         }
  278.         return $container;
  279.     }
  280.     /**
  281.      * Gathers annotation data from class.
  282.      *
  283.      * @param \ReflectionClass $reflectionClass Document reflection class to read mapping from.
  284.      *
  285.      * @return array
  286.      * @throws DocumentParserException
  287.      */
  288.     private function getDocumentReflectionMapping(\ReflectionClass $reflectionClass)
  289.     {
  290.         return $this->parser->parse($reflectionClass);
  291.     }
  292.     /**
  293.      * Returns single document mapping metadata.
  294.      *
  295.      * @param string $namespace Document namespace
  296.      *
  297.      * @return array
  298.      * @throws DocumentParserException
  299.      */
  300.     public function getMapping($namespace)
  301.     {
  302.         $cacheName 'ongr.metadata.document.'.md5($namespace);
  303.         $namespace $this->getClassName($namespace);
  304.         $this->enableCache && $mapping $this->cache->fetch($cacheName);
  305.         if (isset($mapping) && false !== $mapping) {
  306.             return $mapping;
  307.         }
  308.         $mapping $this->getDocumentReflectionMapping(new \ReflectionClass($namespace));
  309.         $this->enableCache && $this->cache->save($cacheName$mapping);
  310.         return $mapping;
  311.     }
  312.     /**
  313.      * Returns fully qualified class name.
  314.      *
  315.      * @param string $className
  316.      * @param string $directory The name of the directory
  317.      *
  318.      * @return string
  319.      */
  320.     public function getClassName($className$directory null)
  321.     {
  322.         return $this->finder->getNamespace($className$directory);
  323.     }
  324. }