Doctrine Search

通用搜索扩展,用于使用不同的文本搜索引擎实现索引和查询ODM/ORM对象。(Generic Search extension for indexing and querying ODM/ORM objects with different text-search engine implementations)

Github stars Tracking Chart

Doctrine Search

注意:此项目目前是原型。有关实际实施示例,请参阅 demo 文件夹。

支持的搜索引擎

特性
  • SearchManager
    • 可以单独使用或以混合配置使用
    • 可配置的搜索管理器支持聚合实体管理器
    • 支持通过搜索引擎适配器(如Elastica)直接进行API调用
    • 根据需要将返回的ID通过批处理操作转换为水合对象
    • 支持可自定义实体处理的事件管理器侦听器
  • 支持通过JMS Serializer通过事件侦听器进行索引或简单的实体回调。
  • 使用ObjectManager::getClassMetadata()作为基础结构创建索引和数据类型的注释

用法

配置

搜索管理器连接可以按照以下示例进行配置:

$config = new Doctrine\Search\Configuration();
$config->setMetadataCacheImpl(new Doctrine\Common\Cache\ArrayCache());
$config->setEntitySerializer(
  new Doctrine\Search\Serializer\JMSSerializer(
    JMS\Serializer\SerializationContext::create()->setGroups('search')
  )
);

$eventManager = new Doctrine\Search\EventManager();
$eventManager->addListener($listener);

$searchManager = new Doctrine\Search\SearchManager(
  $config,
  new Doctrine\Search\ElasticSearch\Client(
    new Elastica\Client(array(
      array('host' => 'localhost', 'port' => '9200')
    )
  ),
  $eventManager
);

Mappings(映射)

索引和类型生成的基本实体映射可以注释,如以下示例所示。映射 可以被渲染成适合使用构建脚本自动生成索引和类型的格式 (需要高级设置)。

<?php
namespace Entities;

use Doctrine\Search\Mapping\Annotations as MAP;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @MAP\ElasticSearchable(index="indexname", type="post", source=true)
 */
class Post
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   * @MAP\ElasticField(type="integer", includeInAll=false)
   */
  private $id;

  /**
   * @ORM\Column(type="string")
   * @MAP\ElasticField(type="string", includeInAll=true, boost=5.0)
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * @MAP\ElasticField(type="string", includeInAll=true)
   */
  private $content;

  /**
   * @MAP\ElasticField(name="tags", type="string", includeInAll=false, index="not_analyzed")
   */
  public function getTags() {
    return $this->tags->slice(0,3);
  }
}

Indexing(索引)

可以通过以下方式序列化文档以进行索引编制。如果需要,事件监听器可以 与本例中所示的ORM一起使用。如果不需要事件侦听器,则实体可以被持久化 或者使用搜索管理器直接删除。

<?php
namespace Entities\Listener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Entities\Behaviour\SearchableEntityInterface;

class SearchableListener implements
{
      protected function getSearchManager() {
            return $this->getDatabaseConnection('elasticsearch');
      }

      public function postPersist(LifecycleEventArgs $oArgs) {
            $oEntity = $oArgs->getEntity();
            if($oEntity instanceof SearchableEntityInterface) {
                $this->getSearchManager()->persist($oEntity);
          }
      }

    public function postRemove(LifecycleEventArgs $oArgs) {
        $oEntity = $oArgs->getEntity();
        if($oEntity instanceof SearchableEntityInterface) {
            $this->getSearchManager()->remove($oEntity);
        }
    }
}

CallbackSerializer

这个方法只需要在实体上使用一个 toArray()方法,不过这个方法可以根据需要进行配置。 只要您的事件侦听器可以识别,本示例中提供的接口可以是您所需的任何接口 需要坚持到搜索引擎的实体(参见上面的例子)。

...
use Entities\Behaviour\SearchableEntityInterface

class Post implements SearchableEntityInterface
{
  ...
  public function toArray() {
    return array(
      'id' => $this->id,
      'title' => $this->title,
      'content' => $this->content
      ...
    );
  }
}

JMS Serializer(JMS序列化)

您也可以使用 JMS Serializer 的高级序列化功能自动处理 根据例如本示例中显示的注释序列化您。

...
use JMS\Serializer\Annotation as JMS;
use Entities\Behaviour\SearchableEntityInterface

/**
 * @ORM\Entity
 * @MAP\ElasticSearchable(index="indexname", type="post", source=true)
 * @JMS\ExclusionPolicy("all")
 */
class Post implements SearchableEntityInterface
{
  ...
  /**
   * @ORM\Column(type="string")
   * @MAP\ElasticField(type="string", includeInAll=true, boost=5.0)
   * @JMS\Expose
   * @JMS\Groups({"public", "search"})
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * @MAP\ElasticField(type="string", includeInAll=true)
   * @JMS\Expose
   * @JMS\Groups({"public", "search"})
   */
  private $content;
  ...
}

AnnotationSerializer

尚未公布。

查询

查询可以通过搜索管理器执行,如下所示。使用结果缓存指的是使用 缓存水合查询。就像这样,搜索引擎特定的适配器接口就会被神奇地暴露出来 例如, Elastica\Query::addSort 方法与 Doctrine\Search\Query 方法合并。因此任何复杂性 支持由搜索引擎客户端库支持的查询。

$hydrationQuery = $entityManager->createQueryBuilder()
  ->select(array('p', 'field(p.id, :ids) as HIDDEN field'))
    ->from('Entities\Post', 'p')
    ->where('p.id IN (:ids)')
    ->orderBy('field')
    ->getQuery();

$query = $searchManager->createQuery()
  ->from('Entities\Post')
  ->searchWith(new Elastica\Query())
    ->hydrateWith($hydrationQuery)
    ->addSort('_score')
    ->setFrom(0)
    ->setLimit(10)
    ->getResult();

通过使用以下技术可以完成简单存储库ID查询和 Term 搜索。反序列化完成 独立于 Doctrine\ORM ,但是相同的模型根据注册的 SerializerInterface 进行水合(hydrated)。

$entity = $searchManager->getRepository('Entities\Post')->find($id);
$entity = $searchManager->getRepository('Entities\Post')->findOneBy(array($key => $term));

Overview

Name With Ownerdoctrine/search
Primary LanguagePHP
Program languagePHP (Language Count: 1)
PlatformBSD, Linux, Mac, Windows
License:MIT License
Release Count2
Last Release Namev0.2 (Posted on )
First Release Namev0.1 (Posted on )
Created At2011-10-12 09:31:05
Pushed At2018-01-03 14:31:42
Last Commit At2016-11-17 18:07:30
Stargazers Count274
Watchers Count29
Fork Count56
Commits Count334
Has Issues Enabled
Issues Count55
Issue Open Count10
Pull Requests Count58
Pull Requests Open Count1
Pull Requests Close Count14
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private

Doctrine Search

Note: This project is a prototype at the moment. See demo folder for practical implementation example.

Supported search engines

Features

  • SearchManager
    • Can be used stand-alone or in a hybrid configuration
    • Configurable search manager supports aggregate entity manager
    • supports direct API calls through search engine adapters such as Elastica
    • transforms returned ID's via batch operation into hydrated objects as required
    • Supports event manager listeners for customizable entity handling
  • Support for indexing through event listeners via JMS Serializer or simple entity callback.
  • Annotations for index and data type creation using ObjectManager::getClassMetadata() as the base structure

#Usage#

Configuration

The search manager connection can be configured as shown in the following example:

$config = new Doctrine\Search\Configuration();
$config->setMetadataCacheImpl(new Doctrine\Common\Cache\ArrayCache());
$config->setEntitySerializer(
  new Doctrine\Search\Serializer\JMSSerializer(
    JMS\Serializer\SerializationContext::create()->setGroups('search')
  )
);

$eventManager = new Doctrine\Search\EventManager();
$eventManager->addListener($listener);

$searchManager = new Doctrine\Search\SearchManager(
  $config,
  new Doctrine\Search\ElasticSearch\Client(
    new Elastica\Client(array(
      array('host' => 'localhost', 'port' => '9200')
    )
  ),
  $eventManager
);

Mappings

Basic entity mappings for index and type generation can be annotated as shown in the following example. Mappings
can be rendered into a format suitable for automatically generating indexes and types using a build script
(advanced setup required).

<?php
namespace Entities;

use Doctrine\Search\Mapping\Annotations as MAP;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @MAP\ElasticSearchable(index="indexname", type="post", source=true)
 */
class Post
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   * @MAP\ElasticField(type="integer", includeInAll=false)
   */
  private $id;

  /**
   * @ORM\Column(type="string")
   * @MAP\ElasticField(type="string", includeInAll=true, boost=5.0)
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * @MAP\ElasticField(type="string", includeInAll=true)
   */
  private $content;

  /**
   * @MAP\ElasticField(name="tags", type="string", includeInAll=false, index="not_analyzed")
   */
  public function getTags() {
    return $this->tags->slice(0,3);
  }
}

Indexing

Documents can be serialized for indexing currently in the following ways. If required an event listener can
be used with your ORM as shown in this example. If an event listener is not needed, entities can be persisted
or removed directly using the search manager.

<?php
namespace Entities\Listener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Entities\Behaviour\SearchableEntityInterface;

class SearchableListener implements
{
      protected function getSearchManager() {
            return $this->getDatabaseConnection('elasticsearch');
      }

      public function postPersist(LifecycleEventArgs $oArgs) {
            $oEntity = $oArgs->getEntity();
            if($oEntity instanceof SearchableEntityInterface) {
                $this->getSearchManager()->persist($oEntity);
          }
      }

    public function postRemove(LifecycleEventArgs $oArgs) {
        $oEntity = $oArgs->getEntity();
        if($oEntity instanceof SearchableEntityInterface) {
            $this->getSearchManager()->remove($oEntity);
        }
    }
}

CallbackSerializer

This approach simply expects a toArray() method on the entity, although this method be configured as required.
The interface suggested in this example can be any interface you desire, as long as your event listener can identify
entities that need to be persisted to the search engine (see above example).

...
use Entities\Behaviour\SearchableEntityInterface

class Post implements SearchableEntityInterface
{
  ...
  public function toArray() {
    return array(
      'id' => $this->id,
      'title' => $this->title,
      'content' => $this->content
      ...
    );
  }
}

JMS Serializer

You can alternatively use the advanced serialization power of the JMS Serializer to automatically handle
serialization for you based on annotations such as those shown in this example.

...
use JMS\Serializer\Annotation as JMS;
use Entities\Behaviour\SearchableEntityInterface

/**
 * @ORM\Entity
 * @MAP\ElasticSearchable(index="indexname", type="post", source=true)
 * @JMS\ExclusionPolicy("all")
 */
class Post implements SearchableEntityInterface
{
  ...
  /**
   * @ORM\Column(type="string")
   * @MAP\ElasticField(type="string", includeInAll=true, boost=5.0)
   * @JMS\Expose
   * @JMS\Groups({"public", "search"})
   */
  private $title;

  /**
   * @ORM\Column(type="text")
   * @MAP\ElasticField(type="string", includeInAll=true)
   * @JMS\Expose
   * @JMS\Groups({"public", "search"})
   */
  private $content;
  ...
}

AnnotationSerializer

Not yet available.

Queries

Queries can be executed through the search manager as shown below. Use of the result cache refers to using the
cache for the hydration query. Search engine specific adapter interfaces are exposed magically so as in this
example, Elastica\Query::addSort method is amalgamated with Doctrine\Search\Query methods. Thus any complexity
of query supported by the search engine client library is supported.

$hydrationQuery = $entityManager->createQueryBuilder()
  ->select(array('p', 'field(p.id, :ids) as HIDDEN field'))
    ->from('Entities\Post', 'p')
    ->where('p.id IN (:ids)')
    ->orderBy('field')
    ->getQuery();

$query = $searchManager->createQuery()
  ->from('Entities\Post')
  ->searchWith(new Elastica\Query())
    ->hydrateWith($hydrationQuery)
    ->addSort('_score')
    ->setFrom(0)
    ->setLimit(10)
    ->getResult();

Simple Repository ID queries and Term search can by done using the following technique. Deserialization is done
independently of Doctrine\ORM but the same models are hydrated according to the registered SerializerInterface.

$entity = $searchManager->getRepository('Entities\Post')->find($id);
$entity = $searchManager->getRepository('Entities\Post')->findOneBy(array($key => $term));
To the top