English spoken conference

Symfony 5: The Fast Track

A new book to learn about developing modern Symfony 5 applications.

Support this project

Caution: You are browsing the legacy symfony 1.x part of this website.

16日目: Webサービス

1.2 / Doctrine

始める前に

6日目にapps/frontend/config/routing.ymljob_show_userという名前の求人情報を閲覧するためにルートを定義したとき、少し定義を間違えました。 ルートは次のようになります:

job_show_user:
  url:     /job/:company_slug/:location_slug/:id/:position_slug
  class:   sfDoctrineRoute
  options:
    model: JobeetJob
    type:  object
    method_for_query: retrieveActiveJob
  param:   { module: job, action: show }
  requirements:
    id: \d+

methodからmethod_for_queryへの変更に注目してください。 これはsymfonyの小さなバグでチュートリアルの間違いなのでプロジェクトを更新する必要があります。

今日のチュートリアルを完了させるためにJobAffiliateスキーマを少し変更して 多対多のリレーションをJobeetCategoryに含める必要があります。 3日目のスキーマもしくは下記に追加されたコードを見れば全スキーマはわかります。

JobeetAffiliate:
  # ...
  relations:
    JobeetCategories:
      class: JobeetCategory
      refClass: JobeetCategoryAffiliate
      local: affiliate_id
      foreign: category_id
      foreignAlias: JobeetAffiliates

変更した後で必ずモデルをリビルドします:

$ php symfony doctrine:build-model

Jobeetのフィード追加に関して、求職者はリアルタイムで新しい求人情報が知らされます。

一方では、求人を投稿するとき、できる限りもっとも注目されたいと願います。 たくさんの小さなWebサイトで求人情報が配信される場合、適任者を見つける機会が増えます。 これがロングテールの力です。 今日開発するWebサービスのおかげでアフィリエイトは自身のWebサイトに投稿された最新の求人情報を公開できます。

アフィリエイト

2日目の要件です:

"ストーリー F7: アフィリエイトは現在アクティブな求人リストを読み取る"

フィクスチャ

アフィリエイト用に新しいフィクスチャファイルを作りましょう:

# data/fixtures/affiliates.yml
JobeetAffiliate:
  sensio_labs:
    url:       http://www.sensio-labs.com/
    email:     [email protected]
    is_active: true
    token:     sensio_labs
    JobeetCategories: [programming]
 
  symfony:
    url:       /
    email:     [email protected]
    is_active: false
    token:     symfony
    JobeetCategories: [design, programming]

多対多のリレーション用のレコード作成はリレーションの名前であるキーを持つ配列を定義することで簡単にできます。 配列の内容はフィクスチャファイルで定義されたオブジェクトの名前です。 異なるファイルからオブジェクトをリンクできますが、まず名前を定義しなければなりません。

フィクスチャファイルにおいて、テスト作業を簡略化するためにトークンは決め打ちされていますが、 実際のユーザーがアカウントを申し込むとき、トークンの生成が必要になります:

// lib/model/doctrine/JobeetAffiliate.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function preValidate($event)
  {
    $object = $event->getInvoker();
 
    if (!$object->getToken())
    {
      $object->setToken(sha1($object->getEmail().rand(11111, 99999)));
    }
  }
 
  // ...
}

データをリロードできます:

$ php symfony doctrine:data-load

求人情報のWebサービス

新しいリソースを作成するとき、最初にURLを定義するのはよい習慣です:

# apps/frontend/config/routing.yml
api_jobs:
  url:     /api/:token/jobs.:sf_format
  class:   sfDoctrineRoute
  param:   { module: api, action: list }
  options: { model: JobeetJob, type: list, method: getForToken }
  requirements:
    sf_format: (?:xml|json|yaml)

このルートに関して、特殊変数sf_formatはURLで終わり妥当な値はxmljsonもしくはyamlです。

アクションがルートに関連するオブジェクトのコレクションを読み取るときに、getForToken()メソッドが呼び出されます。 アフィリエイトがアクティベートされることを確認する必要があるとき、ルートのデフォルトのふるまいをオーバーライドする必要があります:

// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  public function getForToken(array $parameters)
  {
    $affiliate = Doctrine::getTable('JobeetAffiliate') ->findOneByToken($parameters['token']);
    if (!$affiliate || !$affiliate->getIsActive())
    {
      throw new sfError404Exception(sprintf('Affiliate with token "%s" does not exist or is not activated.', $parameters['token']));
    }
 
    return $affiliate->getActiveJobs();
  }
 
  // ...
}

トークンがデータベースに存在しない場合、sfError404Exceptionの例外が投げられます。 この例外クラスは自動的に~404|404エラー~レスポンスに変換されます。 これがモデルクラスから404ページを生成するためのもっともシンプルな方法です。

getForToken()メソッドはgetActiveJobs()という名前の新しいメソッドを使い現在アクティブな求人リストを返します:

// lib/model/doctrine/JobeetAffiliate.class.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function getActiveJobs()
  {
    $q = Doctrine_Query::create()
      ->select('j.*')
      ->from('JobeetJob j')
      ->leftJoin('j.JobeetCategory c')
      ->leftJoin('c.JobeetAffiliates a')
      ->where('a.id = ?', $this->getId());
 
    $q = Doctrine::getTable('JobeetJob')->addActiveJobsQuery($q);
 
    return $q->execute();
  }
 
  // ...
}

最後のステップはapiアクションとテンプレートを作ることです。 generate:moduleタスクでモジュールをブートストラップします:

$ php symfony generate:module frontend api

note

デフォルトのindexアクションを使わないので、アクションクラスからこれを除外し、関連するindexSucess.phpテンプレートを除外します。

アクション

すべてのフォーマットは同じlistアクションを共有します:

// apps/frontend/modules/api/actions/actions.class.php
public function executeList(sfWebRequest $request)
{
  $this->jobs = array();
  foreach ($this->getRoute()->getObjects() as $job)
  {
    $this->jobs[$this->generateUrl('job_show_user', $job, true)] = $job->asArray($request->getHost());
  }
}

テンプレートにJobeetJobオブジェクトの配列を渡す代わりに、文字列の配列を渡します。 同じアクションに対して3つの異なるテンプレートがあるので、値を処理するロジックはJobeetJob::asArray()メソッドで取り除かれました:

// lib/model/doctrine/JobeetJob.class.php
class JobeetJob extends BaseJobeetJob
{
  public function asArray($host)
  {
    return array(
      'category'     => $this->getJobeetCategory()->getName(),
      'type'         => $this->getType(),
      'company'      => $this->getCompany(),
      'logo'         => $this->getLogo() ? 'http://'.$host.'/uploads/jobs/'.$this->getLogo() : null,
      'url'          => $this->getUrl(),
      'position'     => $this->getPosition(),
      'location'     => $this->getLocation(),
      'description'  => $this->getDescription(),
      'how_to_apply' => $this->getHowToApply(),
      'expires_at'   => $this->getCreatedAt(),
    );
  }
 
  // ...
}

xmlフォーマット

テンプレートを作ればxmlフォーマットを簡単にサポートできます:

<!-- apps/frontend/modules/api/templates/listSuccess.xml.php -->
<?xml version="1.0" encoding="utf-8"?>
<jobs>
<?php foreach ($jobs as $url => $job): ?>
  <job url="<?php echo $url ?>">
<?php foreach ($job as $key => $value): ?>
    <<?php echo $key ?>><?php echo $value ?></<?php echo $key ?>>
<?php endforeach; ?>
  </job>
<?php endforeach; ?>
</jobs>

jsonフォーマット

JSONフォーマットのサポートも同じです:

<!-- apps/frontend/modules/api/templates/listSuccess.json.php -->
[
<?php $nb = count($jobs); $i = 0; foreach ($jobs as $url => $job): ++$i ?>
{
  "url": "<?php echo $url ?>",
<?php $nb1 = count($job); $j = 0; foreach ($job as $key => $value): ++$j ?>
  "<?php echo $key ?>": <?php echo json_encode($value).($nb1 == $j ? '' : ',') ?>
 
<?php endforeach; ?>
}<?php echo $nb == $i ? '' : ',' ?>
 
<?php endforeach; ?>
]

yamlフォーマット

組み込みのフォーマットに関して、Content-Typeを変更する、レイアウトを無効にするなどsymfonyはバックグランドで同じコンフィギュレーションを行います。

YAMLフォーマットは組み込みのリクエストフォーマットのリストにないので、レスポンスのContent-Typeは変更可能でレイアウトはアクションでは無効です:

class apiActions extends sfActions
{
  public function executeList(sfWebRequest $request)
  {
    $this->jobs = array();
    foreach ($this->getRoute()->getObjects() as $job)
    {
      $this->jobs[$this->generateUrl('job_show_user', $job, true)] = $job->asArray($request->getHost());
    }
 
    switch ($request->getRequestFormat())
    {
      case 'yaml':
        $this->setLayout(false);
        $this->getResponse()->setContentType('text/yaml');
        break;
    }
  }
}

アクションにおいて、setLayout()メソッドはデフォルトのレイアウトを変更もしくはfalseにセットされているときにそれを無効にします。

YAML用のテンプレートを次の内容を読み込みます:

<!-- apps/frontend/modules/api/templates/listSuccess.yaml.php -->
<?php foreach ($jobs as $url => $job): ?>
-
  url: <?php echo $url ?>
 
<?php foreach ($job as $key => $value): ?>
  <?php echo $key ?>: <?php echo sfYaml::dump($value) ?>
 
<?php endforeach; ?>
<?php endforeach; ?>

有効ではないトークンでWebサービスを呼び出すとき、XMLフォーマットの404ページ、JSONフォーマットの404ページを用意します。しかしYAMLフォーマットに関して、symfonyは何をレンダリングすればよいのかわかりません。

フォーマットを作成するとき、カスタムエラーテンプレートを作らなければなりません。 テンプレートは404ページと他のすべての例外に使われます。

例外は運用環境と開発環境で異なるので、2つのファイルが必要です(デバッグにconfig/error/exception.yaml.php、運用環境にconfig/error/error.yaml.php):

// config/error/exception.yaml.php
<?php echo sfYaml::dump(array(
  'error'       => array(
    'code'      => $code,
    'message'   => $message,
    'debug'     => array(
      'name'    => $name,
      'message' => $message,
      'traces'  => $traces,
    ),
)), 4) ?>
 
// config/error/error.yaml.php
<?php echo sfYaml::dump(array(
  'error'       => array(
    'code'      => $code,
    'message'   => $message,
))) ?>

試す前に、YAMLフォーマット用のレイアウトを作らなければなりません:

// apps/frontend/templates/layout.yaml.php
<?php echo $sf_content ?>

404エラー

tip

組み込みのテンプレートの404エラーと例外をオーバーライドするのは簡単でconfig/error/ディレクトリにファイルを作ります。

Webサービスのテスト

Webサービスをテストするには、アフィリエイトフィクスチャをdata/fixtures/からtest/fixtures/ディレクトリにコピーして自動生成されたapiActionsTest.phpファイルの内容を次のものに置き換えます:

// test/functional/frontend/apiActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
 
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
 
$browser->
  info('1 - Web service security')->
 
  info('  1.1 - A token is needed to access the service')->
  get('/api/foo/jobs.xml')->
  with('response')->isStatusCode(404)->
 
  info('  1.2 - An inactive account cannot access the web service')->
  get('/api/symfony/jobs.xml')->
  with('response')->isStatusCode(404)->
 
  info('2 - The jobs returned are limited to the categories configured for the affiliate')->
  get('/api/sensio_labs/jobs.xml')->
  with('request')->isFormat('xml')->
  with('response')->checkElement('job', 32)->
 
  info('3 - The web service supports the JSON format')->
  get('/api/sensio_labs/jobs.json')->
  with('request')->isFormat('json')->
  with('response')->contains('"category": "Programming"')->
 
  info('4 - The web service supports the YAML format')->
  get('/api/sensio_labs/jobs.yaml')->
  with('response')->begin()->
    isHeader('content-type', 'text/yaml; charset=utf-8')->
    contains('category: Programming')->
  end()
;

このテストにおいて、2つの新しいメソッドに注目します:

  • isFormat(): これはリクエストのフォーマットをテストします
  • contains(): 非HTMLフォーマットに関して、レスポンスが テキストの期待されたスニペットを含むのかチェックします

アフィリエイトアプリケーションのフォーム

Webサービスを提供する準備ができたので、アフィリエイト用のアカウント作成フォームを作りましょう。 再度新しい機能をアプリケーションに追加することで古典的なプロセスを説明します。

ルーティング

ご想像の通り、ルートは最初に作るものです:

# apps/frontend/config/routing.yml
affiliate:
  class:   sfDoctrineRouteCollection
  options:
    model: JobeetAffiliate
    actions: [new, create]
    object_actions: { wait: get }

これは新しい設定オプション: actionsを持つ古典的なDoctrineコレクションルートです。 ルートによって定義されるデフォルトの7つのアクションすべてが不要なので、actionsオプションはnewcreateアクションのみにマッチするようにルートに指示します。 追加のwaitルートはまもなくアフィリエイトになる人にアカウントに関するフィードバックをします。

ブートストラップする

2番目のステップはモジュールの生成です:

$ php symfony doctrine:generate-module frontend affiliate JobeetAffiliate --non-verbose-templates

テンプレート

doctrine:generate-moduleタスクは古典的な7つのアクションとそれらに対応するテンプレートを生成します。 templates/ディレクトリにおいて、_form.phpnewSuccess.php以外のすべてのファイルを削除します。 維持するファイルに関して、これらの内容を次のものに置き換えます:

<!-- apps/frontend/modules/affiliate/templates/newSuccess.php -->
<?php use_stylesheet('job.css') ?>
 
<h1>Become an Affiliate</h1>
 
<?php include_partial('form', array('form' => $form)) ?>
 
<!-- apps/frontend/modules/affiliate/templates/_form.php -->
<?php include_stylesheets_for_form($form) ?>
<?php include_javascripts_for_form($form) ?>
 
<?php echo form_tag_for($form, 'affiliate') ?>
  <table id="job_form">
    <tfoot>
      <tr>
        <td colspan="2">
          <input type="submit" value="Submit" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form ?>
    </tbody>
  </table>
</form>

waitSuccess.phpテンプレートを作ります:

<!-- apps/frontend/modules/affiliate/templates/waitSuccess.php -->
<h1>Your affiliate account has been created</h1>
 
<div style="padding: 20px">
  Thank you!
  You will receive an email with your affiliate token
  as soon as your account will be activated.
</div>

最後に、affiliateモジュールを指し示すようにフッターのリンクを変更します:

// apps/frontend/templates/layout.php
<li class="last">
  <a href="<?php echo url_for('@affiliate_new') ?>">Become an affiliate</a>
</li>

アクション

ここで繰り返しますが、作成フォームのみを使うので、actions.class.phpファイルを開きexecuteNew()executeCreate()processForm()以外のすべてのメソッドを削除します。

processForm()アクションに関して、リダイレクトURLをwaitアクションに変更します:

// apps/frontend/modules/affiliate/actions/actions.class.php
$this->redirect($this->generateUrl('affiliate_wait', $jobeet_affiliate));

waitアクションはシンプルなのでテンプレートに何も渡さずに済みます:

// apps/frontend/modules/affiliate/actions/actions.class.php
public function executeWait()
{
}

アフィリエイトはトークンを選択したり、アカウントを即座にアクティベートできません。 フォームをカスタマイズするためにJobeetAffiliateFormファイルを開きます:

// lib/form/doctrine/JobeetAffiliateForm.class.php 
class JobeetAffiliateForm extends BaseJobeetAffiliateForm
{
  public function configure()
  {
    unset($this['is_active'], $this['token'], $this['created_at'], $this['updated_at']);
 
    $this->widgetSchema['jobeet_categories_list']->setOption('expanded', true);
    $this->widgetSchema['jobeet_categories_list']->setLabel('Categories');
 
    $this->validatorSchema['jobeet_categories_list']->setOption('required', true);
 
    $this->widgetSchema['url']->setLabel('Your website URL');
    $this->widgetSchema['url']->setAttribute('size', 50);
 
    $this->widgetSchema['email']->setAttribute('size', 50);
 
    $this->validatorSchema['email'] = new sfValidatorEmail(array('required' => true));
  }
}

フォームフレームワークは他のカラムのように多対多のリレーションをサポートします。 デフォルトでは、sfWidgetFormChoiceウィジェットのおかげでこのようなリレーションはドロップダウンボックスのようなレンダラーです。 10日目で見たように、expandedオプションを指定してレンダリングされたタグを変更しました。

EメールとURLはinputタグのデフォルトサイズよりも長くなりがちなので、デフォルトのHTML属性はsetAttribute()メソッドを使用して設定できます。

アフィリエイトのフォーム

テスト

最後のステップは新しい機能に対して機能テストを書くことです。

affiliateモジュール用の生成テストを次のコードで置き換えます:

// test/functional/frontend/affiliateActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
 
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
 
$browser->
  info('1 - An affiliate can create an account')->
 
  get('/affiliate/new')->
  click('Submit', array('jobeet_affiliate' => array(
    'url'                            => 'http://www.example.com/',
    'email'                          => '[email protected]',
    'jobeet_categories_list'         => array(Doctrine::getTable('JobeetCategory')->findOneBySlug('programming')->getId()),
  )))->
  isRedirected()->
  followRedirect()->
  with('response')->checkElement('#content h1', 'Your affiliate account has been created')->
 
  info('2 - An affiliate must at least select one category')->
 
  get('/affiliate/new')->
  click('Submit', array('jobeet_affiliate' => array(
    'url'   => 'http://www.example.com/',
    'email' => '[email protected]',
  )))->
  with('form')->isError('jobeet_categories_list')
;

アフィリエイトのバックエンド

バックエンドに関して、管理者によってアフィリエイトをアクティベートするためにaffiliateモジュールを作成しなければなりません:

$ php symfony doctrine:generate-admin backend JobeetAffiliate --module=affiliate

新しく作成されたモジュールにアクセスするには、アクティベートされるアフィリエイトの人数を伴うメインメニューにリンクを追加します:

<!-- apps/backend/templates/layout.php -->
<li>
  <a href="<?php echo url_for('@jobeet_affiliate_affiliate') ?>">
    Affiliates - <strong><?php echo Doctrine::getTable('JobeetAffiliate')->countToBeActivated() ?></strong>
  </a>
</li>
 
// lib/model/doctrine/JobeetAffiliateTable.class.php
class JobeetAffiliateTable extends Doctrine_Table
{
  public function countToBeActivated()
  {
    $q = $this->createQuery('a')
      ->where('a.is_active = ?', 0);
 
    return $q->count();
  }
 
  // ...
 
}

バックエンドで必要なアクションのみがアカウントをアクティベートするもしくはアクティベートを解除するので、インターフェイスを少し簡略化するためにconfigセクションのデフォルトのジェネレーターを変更しlistビューからアカウントを直接アクティベートするリンクを追加します:

# apps/backend/modules/affiliate/config/generator.yml
config:
  fields:
    is_active: { label: Active? }
  list:
    title:   Affiliate Management
    display: [is_active, url, email, token]
    sort:    [is_active]
    object_actions:
      activate:   ~
      deactivate: ~
    batch_actions:
      activate:   ~
      deactivate: ~
    actions: {}
  filter:
    display: [url, email, is_active]

管理者をより生産的にするために、アクティベートされるアフィリエイトのみを表示するようにデフォルトのフィルターを変更します:

// apps/backend/modules/affiliate/lib/affiliateGeneratorConfiguration.class.php
class affiliateGeneratorConfiguration extends BaseAffiliateGeneratorConfiguration
{
  public function getFilterDefaults()
  {
    return array('is_active' => '0');
  }
}

書く必要があるのはactivatedeactivateアクションに対するコードだけです:

// apps/backend/modules/affiliate/actions/actions.class.php
class affiliateActions extends autoAffiliateActions
{
  public function executeListActivate()
  {
    $this->getRoute()->getObject()->activate();
 
    $this->redirect('@jobeet_affiliate_affiliate');
  }
 
  public function executeListDeactivate()
  {
    $this->getRoute()->getObject()->deactivate();
 
    $this->redirect('@jobeet_affiliate_affiliate');
  }
 
  public function executeBatchActivate(sfWebRequest $request)
  {
    $q = Doctrine_Query::create()
      ->from('JobeetAffiliate a')
      ->whereIn('a.id', $request->getParameter('ids'));
 
    $affiliates = $q->execute();
 
    foreach ($affiliates as $affiliate)
    {
      $affiliate->activate();
    }
 
    $this->redirect('@jobeet_affiliate_affiliate');
  }
 
  public function executeBatchDeactivate(sfWebRequest $request)
  {
    $q = Doctrine_Query::create()
      ->from('JobeetAffiliate a')
      ->whereIn('a.id', $request->getParameter('ids'));
 
    $affiliates = $q->execute();
 
    foreach ($affiliates as $affiliate)
    {
      $affiliate->deactivate();
    }
 
    $this->redirect('@jobeet_affiliate_affiliate');
  }
}
 
// lib/model/doctrine/JobeetAffiliate.class.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function activate()
  {
    $this->setIsActive(true);
 
    return $this->save();
  }
 
  public function deactivate()
  {
    $this->setIsActive(false);
 
    return $this->save();
  }
 
  // ...
}

アフィリエイトのバックエンド

Eメールを送信する

アフィリエイトのアカウントが管理者によってのみ変更されるとき、Eメールは購読を確認にトークンを渡すためにアフィリエイトに送信されます。

PHPにはSwiftMailerZend_MailezcMailのようなEメールを送信するためのよいライブラリがたくさんあります。 来たる日に他のZend Frameworkライブラリを使うので、Eメールを送信するのにZend_Mailを使いましょう。

Zend Frameworkのインストールと設定

Zend MailライブラリはZend Frameworkの一部です。 Zend Frameworkのすべては必要ないのでsymfonyフレームワーク自身と並行して必要なパーツだけをlib/vendor/ディレクトリにインストールします。

最初に、 Zend Frameworkをダウンロードしてlib/vendor/Zend/ディレクトリがあるようにファイルを展開します。

note

次の説明内容はZend Framework 1.8.0でテストしました。

次のファイルとディレクトリ以外のすべてを削除してディレクトリをクリーンナップします:

  • Exception.php
  • Loader/
  • Loader.php
  • Mail/
  • Mail.php
  • Mime/
  • Mime.php
  • Search/

note

Eメール送信のためにSearch/ディレクトリは必要ありませんが明日のチュートリアルで必要です。

それから、Zendオートローダーを登録するシンプルな方法を提供するために次のコードをProjectConfigurationクラスに追加します:

// config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
  static protected $zendLoaded = false;
 
  static public function registerZend()
  {
    if (self::$zendLoaded)
    {
      return;
    }
 
    set_include_path(sfConfig::get('sf_lib_dir').'/vendor'.PATH_SEPARATOR.get_include_path());
    require_once sfConfig::get('sf_lib_dir').'/vendor/Zend/Loader.php';
    Zend_Loader_Autoloader::getInstance();
    self::$zendLoaded = true;
  }
 
  // ...
}

Eメールを送信する

管理者がアフィリエイトをバリデートする際にEメールを送信できるようにactivateアクションを編集します:

// apps/backend/modules/affiliate/actions/actions.class.php
class affiliateActions extends autoAffiliateActions
{
  public function executeListActivate()
  {
    $affiliate = $this->getRoute()->getObject();
    $affiliate->activate();
 
    // アフィリエイトにEメールを送信する
    ProjectConfiguration::registerZend();
    $mail = new Zend_Mail();
    $mail->setBodyText(<<<EOF
Your Jobeet affiliate account has been activated.
 
Your token is {$affiliate->getToken()}.
 
The Jobeet Bot.
EOF
);
    $mail->setFrom('[email protected]', 'Jobeet Bot');
    $mail->addTo($affiliate->getEmail());
    $mail->setSubject('Jobeet affiliate token');
    $mail->send();
 
    $this->redirect('@jobeet_affiliate_affiliate');
  }
 
  // ...
}

動作するコードに関して、[email protected]を本当のEメールアドレスに変更する必要があります。

note

Zend_Mailライブラリの全チュートリアルはZend Frameworkの公式サイトで読めます。

また明日

symfonyのRESTアーキテクチャのおかげで、プロジェクト用にWebサービスを実装するのはとても簡単です。 しかしながら、今日は読み込みだけのWebサービス用のコードを書いたので、Webサービスの読み書き機能を実装するためのsymfonyの知識は十分にあります。

プロジェクトに新しい機能を追加するプロセスに慣れ親しんでいるので、フロントエンドと対応するバックエンドでアフィリエイトアカウント作成フォームの実装は本当に簡単でした。

2日目の要件を覚えているなら次のとおりです:

"アフィリエイトは返される求人の件数を制限することおよび、カテゴリを指定することでクエリを絞りこむこともできる。"

この機能の実装は簡単なので今夜やってみましょう。

明日は、JobeetのWebサイトに欠けている最後の機能である検索エンジンを実装します。