Jobeet 17日目:検索エンジン


すべての友达に来訪した友达に書きます!
Friday,December 12,2008 symfonyを勉強したいのですが、インターネットで勉強資料がこんなに探しにくいことを知りました.だからこのウェブサイトを建てて、学習の過程の中で出会った問題、提供できる資料をすべて出します.他の勉強中の友達にも助けてほしいし、友達にも助けてもらいたい.ネット上では比較的良いsymfony中フォーラムは発見されなかったので、このサイトを建てると同時に1つも建てました.皆さんの交流のプラットフォームになることを望んでいます.
Read More »
Filed Under: Technology 3 Comments

Popular Articles
  • すべての友达に来訪した友达に書きます!
  • sfGuardPlugin構成(続き)
  • Jobeet 2日目:プロジェクト
  • Jobeet 3日目:データモデル
  • Jobeet 4日目:コントローラとビュー(1)
  • Jobeet 4日目:コントローラとビュー(2)
  • Jobeet 5日目:ルーティング構成



  • Jobeet 17日目:検索エンジン
    転載明記出典:http://symfony.lag.cnLife@fterhttp://www.lag.cn英語原版:http://www.symfony-project.org/jobeet/1_2/Propel/en/17
    レビュー
    2日前にfeedを追加し、ユーザーが最新の求人情報を購読できるようにしました.今日、私たちは引き続きユーザー体験を向上させ、Jobeetの最後の主要機能である検索エンジンを実現します.
    The Technology
    仕事をする前に、symfonyの歴史を少し理解します.コードテスト、再構築などの良い習慣を常に提唱し、symfonyフレームワークを開発する際にも応用してみましょう.「車輪を再発明しないでください」は私たちの座右の銘です.実際、4年前にsymfonyの開発を開始したとき、MojaviとPropelの2つのオープンソースソフトウェアを組み合わせて、再開発しませんでした.そのため、新しい問題を解決するたびに、コードの作成に追われるのではなく、既存のライブラリがあるかどうかを探しています.
    今日、私たちが追加する検索プログラムも既存のライブラリを使用しています.Zend Lucene、これはZendフレームワークのライブラリで、有名なJava Luceneプロジェクトのポートです.
    Zend Luceneドキュメントで、このライブラリについて説明します.
    ...PHP 5で書かれた汎用テキスト検索エンジン.ファイルシステムを使用してインデックスを格納するため、データベースのサポートは必要ありません.そのため、ほとんどのPHPサイトで使用できます.Zend_Search_Luceneは次の機能をサポートします.
  • 等分配列検索結果–最も良い結果が一番前に表示される
  • 複数のリクエスト方式:フレーズリクエスト、ブールリクエスト、ワイルドカードリクエスト、ファジイリクエスト、範囲リクエスト等
  • タイトル、作成者、コンテンツなどのフィールド別検索
  • ここではZend Lucen eライブラリの使用を多く紹介するのではなく、symfonyでどのように使用するかに重点を置いています.より広く言えばsymfonyでサードパーティ製ソフトウェアをどのように使用するかです.Zend Luceneの詳細については、Zend Luceneドキュメントを参照してください.
    昨日Zend Frameworkのメールライブラリをインストールした際には、既にZend Luceneライブラリがインストールされていました.
    索引
    ユーザーがキーワードを入力すると、Jobeet検索エンジンはそれに一致するすべての作業を返します.すべての作業にインデックスを作成する必要があります.検索エンジンが正常になり、インデックスファイルがdata/ディレクトリに格納されます.
    Zend Luceneは、インデックスが存在するかどうかにかかわらず、インデックスファイルにアクセスする2つのメソッドを提供します.したがって、JobeetJobPeerでhelperメソッドを作成し、インデックスが存在する場合はインデックスを返し、存在しない場合は新しいインデックスファイルを作成する必要があります.
    // lib/model/JobeetJobPeer.php
    static public function getLuceneIndex()
    {
      ProjectConfiguration::registerZend();
     
      if (file_exists($index = self::getLuceneIndexFile()))
      {
        return Zend_Search_Lucene::open($index);
      }
      else
      {
        return Zend_Search_Lucene::create($index);
      }
    }
     
    static public function getLuceneIndexFile()
    {
      return sfConfig::get('sf_data_dir').'/job.'.sfConfig::get('sf_environment').'.index';
    }
     

    The save() method
    インデックス・ファイルも、ジョブの作成、更新、または削除のたびに更新する必要があります.ジョブ情報をデータに保存するたびにインデックスファイルが更新されることを保証するには、JobeetJobのsave()メソッドを編集する必要があります.
    // lib/model/JobeetJob.php
    public function save(PropelPDO $con = null)
    {
      // ...
     
      $ret = parent::save($con);
     
      $this->updateLuceneIndex();
     
      return $ret;
    }
     

    updateLuceneIndex()メソッドを作成し、実際の作業を行います.
    // lib/model/JobeetJob.php
    public function updateLuceneIndex()
    {
      $index = JobeetJobPeer::getLuceneIndex();
     
      // remove an existing entry
      if ($hit = $index->find('pk:'.$this->getId()))
      {
        $index->delete($hit->id);
      }
     
      // don't index expired and non-activated jobs
      if ($this->isExpired() || !$this->getIsActivated())
      {
        return;
      }
     
      $doc = new Zend_Search_Lucene_Document();
     
      // store job primary key URL to identify it in the search results
      $doc->addField(Zend_Search_Lucene_Field::UnIndexed('pk', $this->getId()));
     
      // index job fields
      $doc->addField(Zend_Search_Lucene_Field::UnStored('position', $this->getPosition(), 'utf-8'));
      $doc->addField(Zend_Search_Lucene_Field::UnStored('company', $this->getCompany(), 'utf-8'));
      $doc->addField(Zend_Search_Lucene_Field::UnStored('location', $this->getLocation(), 'utf-8'));
      $doc->addField(Zend_Search_Lucene_Field::UnStored('description', $this->getDescription(), 'utf-8'));
     
      // add job to the index
      $index->addDocument($doc);
      $index->commit();
    }
     

    Zend Luceneはインデックスに存在するレコードを更新できないため、既存のレコードを更新する必要がある場合は、まずこのレコードを削除する必要があります.
    作業インデックスの作成自体は簡単です.プライマリ・キー(pk)を格納する役割は、検索結果の作業URLのパラメータとして、ユーザーがURLを通じて対応する作業ページにアクセスできることです.インデックスファイルに格納されるメインフィールド(position,company,location,description)は、検索内容を一致させるために使用されます.
    Propel異常処理
    データベースにジョブを格納したり、インデックスファイルを書き込むのに失敗したり、データベースにジョブを格納して失敗したりしますが、このレコードが書き込まれたインデックスファイルにはどうすればいいのでしょうか.PropelもZend Luceneも異常を投げ出す.場合によっては、データベースに作業を保存したが、対応するインデックスは生成されなかった可能性があります.このような状況を防ぐために、エラーが発生した場合にトランザクションをロールバックする2つの状況を例外処理に入れることができます.
    // lib/model/JobeetJob.php
    public function save(PropelPDO $con = null)
    {
      // ...
     
      if (is_null($con))
      {
        $con = Propel::getConnection(JobeetJobPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
      }
     
      $con->beginTransaction();
      try
      {
        $ret = parent::save($con);
     
        $this->updateLuceneIndex();
     
        $con->commit();
     
        return $ret;
      }
      catch (Exception $e)
      {
        $con->rollBack();
        throw $e;
      }
    }
     

    delete()
    同様にdelete()メソッドを上書きする必要があります.データベースからレコードを削除すると、インデックスファイルから対応するレコードを削除します.
    // lib/model/JobeetJob.php
    public function delete(PropelPDO $con = null)
    {
      $index = JobeetJobPeer::getLuceneIndex();
     
      if ($hit = $index->find('pk:'.$this->getId()))
      {
        $index->delete($hit->id);
      }
     
      return parent::delete($con);
    }
     

    Mass delete
    propel:data-loadを使用して初期化データをインポートすると、symfonyはJobeetJobPeer::doDeleteAll()メソッドを呼び出すことで、存在するすべての作業記録を削除します.このメソッドを変更して、すべてのレコードを削除しながらすべてのインデックスファイルを削除します.
    // lib/model/JobeetJobPeer.php
    public static function doDeleteAll($con = null)
    {
      if (file_exists($index = self::getLuceneIndexFile()))
      {
        sfToolkit::clearDirectory($index);
        rmdir($index);
      }
     
      return parent::doDeleteAll($con);
    }
     

    Searching
    はい、準備は整いました.データをインポートし、インデックスファイルを生成します.
    $ php symfony propel:data-load --env=dev
    

    コマンドで使用する-envオプションは、dev環境でインデックスが実行されることを指定します.デフォルト環境はcliです.
    Unixユーザーの場合:インデックスはコマンドラインから、Webから変更される可能性があるため、両方の場合、インデックスディレクトリが書き込み可能であることを同時に保証する必要があります.
    You might have some warnings about the ZipArchive class if you don’t havethe zip extension compiled in your PHP. It’s a known bug of the Zend_Loader class.
    フロントでの検索は非常に簡単です.まず、ルーティングを作成します.
    job_search:
      url:   /search
      param: { module: job, action: search }
     

    次に、アクション:
    // apps/frontend/modules/job/actions/actions.class.php
    class jobActions extends sfActions
    {
      public function executeSearch(sfWebRequest $request)
      {
        if (!$query = $request->getParameter('query'))
        {
          return $this->forward('job', 'index');
        }
     
        $this->jobs = JobeetJobPeer::getForLuceneQuery($query);
      }
     
      // ...
    }
     

    テンプレートも簡単です.
    // apps/frontend/modules/job/templates/searchSuccess.php
    <?php use_stylesheet('jobs.css') ?>
     
    <div id="jobs">
      <?php include_partial('job/list', array('jobs' => $jobs)) ?>
    </div>
     

    直接使用するメソッドを検索getForLuceneQuery()
    // lib/model/JobeetJobPeer.php
    static public function getForLuceneQuery($query)
    {
      $hits = self::getLuceneIndex()->find($query);
     
      $pks = array();
      foreach ($hits as $hit)
      {
        $pks[] = $hit->pk;
      }
     
      $criteria = new Criteria();
      $criteria->add(self::ID, $pks, Criteria::IN);
      $criteria->setLimit(20);
     
      return self::doSelect(self::addActiveJobsCriteria($criteria));
    }
     

    Luceneインデックスからすべての結果を取得し、非アクティブな作業をフィルタリングし、結果を20レコードに制限しました.
    更新layout:
    // apps/frontend/templates/layout.php
    <h2>Ask for a job</h2>
    <form action="<?php echo url_for('@job_search') ?>" method="get">
      <input type="text" name="query" value="<?php echo $sf_request->getParameter('query') ?>" id="search_keywords" />
      <input type="submit" value="search" />
      <div class="help">
        Enter some keywords (city, country, position, ...)
      </div>
    </form>
     

    Zend Luceneは豊富なクエリーメカニズムを提供し、ブール、ワイルドカード、ファジイ検索などをサポートしています.参考Zend Luceneマニュアル.
    Unit Tests
    検索エンジンに対してどのようなテストが必要ですか?Zend Luceneフレームワーク自体をテストしないのは明らかですが、JobeetJobクラスに統合されています.
    次のテストをJobeetJobTestに追加します.phpファイルの末尾に、更新テストの数を忘れないでください.
    // test/unit/model/JobeetJobTest.php
    $t->comment('->getForLuceneQuery()');
    $job = create_job(array('position' => 'foobar', 'is_activated' => false));
    $job->save();
    $jobs = JobeetJobPeer::getForLuceneQuery('position:foobar');
    $t->is(count($jobs), 0, '::getForLuceneQuery() does not return non activated jobs');
     
    $job = create_job(array('position' => 'foobar', 'is_activated' => true));
    $job->save();
    $jobs = JobeetJobPeer::getForLuceneQuery('position:foobar');
    $t->is(count($jobs), 1, '::getForLuceneQuery() returns jobs matching the criteria');
    $t->is($jobs[0]->getId(), $job->getId(), '::getForLuceneQuery() returns jobs matching the criteria');
     
    $job->delete();
    $jobs = JobeetJobPeer::getForLuceneQuery('position:foobar');
    $t->is(count($jobs), 0, '::getForLuceneQuery() does not return delete jobs');
     

    検索結果に表示されないか、または削除された作業をテストします.一致条件の作業が結果に表示されるかどうかもテストした.
    Tasks
    最後に、タスクのクリーンアップが期限切れになったインデックス(たとえば、作業が期限切れになった場合)を作成し、インデックスを最適化する必要があります.cleanupタスクがあるので、上記の機能を追加するだけでいいです.
    // lib/task/JobeetCleanupTask.class.php
    protected function execute($arguments = array(), $options = array())
    {
      $databaseManager = new sfDatabaseManager($this->configuration);
     
      // cleanup Lucene index
      $index = JobeetJobPeer::getLuceneIndex();
     
      $criteria = new Criteria();
      $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::LESS_THAN);
      $jobs = JobeetJobPeer::doSelect($criteria);
      foreach ($jobs as $job)
      {
        if ($hit = $index->find('pk:'.$job->getId()))
        {
          $hit->delete();
        }
      }
     
      $index->optimize();
     
      $this->logSection('lucene', 'Cleaned up and optimized the job index');
     
      // Remove stale jobs
      $nb = JobeetJobPeer::cleanup($options['days']);
     
      $this->logSection('propel', sprintf('Removed %d stale jobs', $nb));
    }
     

    上記の作業では、インデックスから期限切れの求人情報をすべて削除し、Zend Luceneに組み込まれたoptimize()最適化方法に感謝します.
    See you Tomorrow
    今日は1時間もかからずに完全な検索エンジンを完成しました.新しい機能を追加したいときは、他の場所がこの問題を解決しているかどうかを見てみましょう.まず、見てみるとsymfony frameworkすでにこのような機能があります.同時に確認するのを忘れないでくださいsymfony plugins.And don’t forgetto check theZend Framework librariesおよびezComponent.
    明日は非侵入JavaScriptコードを使用して、検索エンジンの反応能力を高めます.ユーザーが入力した内容に基づいて、検索結果をリアルタイムで更新します.もちろん、symfonyでのAJAXの使い方も適時に説明します.
    Categories: Jobeet中国語版