Author: Annie
Date: 2010-01-16 15:59:44 +0100 (Sat, 16 Jan 2010)
New Revision: 26718
Added:
doc/branches/1.2/jobeet/ru/16.txt
Modified:
doc/branches/1.2/jobeet/ru/06.txt
Log:
Partial russian translation of Jobeet 1.2, lesson 6 and have been updated and
lesson 16 has been added.
Modified: doc/branches/1.2/jobeet/ru/06.txt
===================================================================
--- doc/branches/1.2/jobeet/ru/06.txt 2010-01-16 14:34:51 UTC (rev 26717)
+++ doc/branches/1.2/jobeet/ru/06.txt 2010-01-16 14:59:44 UTC (rev 26718)
@@ -308,8 +308,8 @@
SELECT `position`, `created_at`, `expires_at` FROM `jobeet_job`;
-Конфигурирование.
------------------
+Конфигурирование
+----------------
В методе `JobeetJob::save()` мы жестко задали количество дней, через которое
вакансия становится просроченой. Неплохо было бы сделать это значение
конфигурируемым.
@@ -761,8 +761,8 @@

-Обезопасим страницу вакансии.
------------------------------
+Обезопасим страницу вакансии
+----------------------------
Когда вакансия истекает, она не должна быть доступна, даже если вы попытаетесь
открыть ее по прямой ссылке. Откройте url просроченой вакансии (замените
значение `id`
Added: doc/branches/1.2/jobeet/ru/16.txt
===================================================================
--- doc/branches/1.2/jobeet/ru/16.txt (rev 0)
+++ doc/branches/1.2/jobeet/ru/16.txt 2010-01-16 14:59:44 UTC (rev 26718)
@@ -0,0 +1,1085 @@
+День 16: Веб-сервисы
+====================
+
+<doctrine>
+Прежде, чем начнем
+------------------
+
+Нам необходимо сделать маленькое изменение в схеме `JobAffiliate`, для того,
+чтобы определить отношение многие ко многим с таблицей `JobeetCategory`.
+Вы можете посмотреть всю схему в главе "День 3" или просто посмотрите, что
нужно добавить:
+
+ [yml]
+ JobeetAffiliate:
+ # ...
+ relations:
+ JobeetCategories:
+ class: JobeetCategory
+ refClass: JobeetCategoryAffiliate
+ local: affiliate_id
+ foreign: category_id
+ foreignAlias: JobeetAffiliates
+
+Не забудьте перестроить модели после внесения изменений:
+
+ $ php symfony doctrine:build-model
+</doctrine>
+
+С появлением новостных лент (feeds) на сайте Jobeet, клиенты могут быть
+информированы о новых вакансиях в реальном времени.
+
+С другой стороны, когда Вы добавляете новую вакансию, Вы хотите чтобы она
+распространилась как можно шире. Если Ваше предложение о вакансии будет
упоминаться
+на множестве некрупных сайтов, шанс, что Вы найдете нужного Вам человека
увеличится.
+В этом сила [Long Tail](http://en.wikipedia.org/wiki/The_Long_Tail).
+Благодаря веб-сервисам, которые мы разработаем сегодня, партнеры получат
+возможность размещать последние вакансии на своих сайтах.
+
+Партнеры
+------------
+
+Напомним требования из урока 2:
+
+ "История F7: Партнер получает текущий список активных вакансий"
+
+### Начальные данные (fixtures)
+
+Давайте создадим новый файл с начальными данными для партнеров:
+
+ [yml]
+<propel>
+ # data/fixtures/030_affiliates.yml
+</propel>
+<doctrine>
+ # data/fixtures/affiliates.yml
+</doctrine>
+ JobeetAffiliate:
+ sensio_labs:
+ url: http://www.sensio-labs.com/
+ email: [email protected]
+ is_active: true
+ token: sensio_labs
+<propel>
+ jobeet_category_affiliates: [programming]
+</propel>
+<doctrine>
+ JobeetCategories: [programming]
+</doctrine>
+
+ symfony:
+ url: http://www.symfony-project.org/
+ email: [email protected]
+ is_active: false
+ token: symfony
+<propel>
+ jobeet_category_affiliates: [design, programming]
+</propel>
+<doctrine>
+ JobeetCategories: [design, programming]
+</doctrine>
+
+<propel>
+Добавить запись для связующей таблицы в отношении многие ко многим легко.
+Просто объявите массив, ключом которого будет название связующей таблицы и
добавьте
+к этому имени букву `s`.
+</propel>
+<doctrine>
+Добавить записи для отношения многие ко многим легко. Просто объявите массив,
+ключом которого будет название самой связи.
+</doctrine>
+В массиве содержатся имена объектов, которые описаны в файле с начальными
данными
+(fixtures). Вы можете связывать объекты из разных файлов, но эти имена должны
быть
+объявлены ранее.
+В файле с начальными данными, токены прописаны вручную, чтобы упростить
+тестирование, но когда пользователь запрашивает аккаунт, токен должен быть
сгенерирован.
+
+<propel>
+ [php]
+ // lib/model/JobeetAffiliate.php
+ class JobeetAffiliate extends BaseJobeetAffiliate
+ {
+ public function save(PropelPDO $con = null)
+ {
+ if (!$this->getToken())
+ {
+ $this->setToken(sha1($this->getEmail().rand(11111, 99999)));
+ }
+
+ return parent::save($con);
+ }
+
+ // ...
+ }
+</propel>
+<doctrine>
+ [php]
+ // 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)));
+ }
+ }
+
+ // ...
+ }
+</doctrine>
+
+Теперь вы можете загрузить начальные данные:
+
+ $ php symfony propel:data-load
+
+### Веб-сервис вакансий
+
+Как всегда, первым делом, когда Вы создаете новый ресурс, хорошей привычкой
служит определение
+URL в начале:
+
+ [yml]
+ # apps/frontend/config/routing.yml
+ api_jobs:
+ url: /api/:token/jobs.:sf_format
+ class: sfPropelRoute
+ param: { module: api, action: list }
+ options: { model: JobeetJob, type: list, method: getForToken }
+ requirements:
+ sf_format: (?:xml|json|yaml)
+
+Для этого маршрута, указанна специальная переменная `sf_format`, которая
находится
+в конце URL, и соответствующие ей значения будут `xml`, `json` или `yaml`.
+
+Метод `getForToken()` вызывается, когда действие запрашивает коллекцию
объектов,
+связанных с маршрутом. Так как нам надо удостовериться, что партнер
активирован,
+мы переопределим поведение маршрута по умолчанию:
+
+<propel>
+ [php]
+ // lib/model/JobeetJobPeer.php
+ class JobeetJobPeer extends BaseJobeetJobPeer
+ {
+ static public function getForToken(array $parameters)
+ {
+ $affiliate = JobeetAffiliatePeer::getByToken($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();
+ }
+
+ // ...
+ }
+</propel>
+<doctrine>
+ [php]
+ // 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();
+ }
+
+ // ...
+ }
+</doctrine>
+
+Если токен не существует в базе данных, мы генерируем исключение
+`sfError404Exception`. Этот класс исключений затем автоматически преобразуется
в
+Ошибку 404. Это простейший способ сгенерировать Ошибку 404 из модели.
+
+<propel>
+Метод `getForToken()` использует два новых метода, которые мы сейчас создадим.
+
+Сначала метод `getByToken()` должен быть создан для получения партнера по его
+токену:
+
+ [php]
+ // lib/model/JobeetAffiliatePeer.php
+ class JobeetAffiliatePeer extends BaseJobeetAffiliatePeer
+ {
+ static public function getByToken($token)
+ {
+ $criteria = new Criteria();
+ $criteria->add(self::TOKEN, $token);
+
+ return self::doSelectOne($criteria);
+ }
+ }
+
+Затем метод `getActiveJobs()` возвращает список текущих активных вакансий для
+выбранных партнером категорий:
+</propel>
+<doctrine>
+Метод `getForToken()` использует новый метод `getActiveJobs()` и возвращает
список
+текущих активных вакансий.
+</doctrine>
+
+<propel>
+ [php]
+ // lib/model/JobeetAffiliate.php
+ class JobeetAffiliate extends BaseJobeetAffiliate
+ {
+ public function getActiveJobs()
+ {
+ $cas = $this->getJobeetCategoryAffiliates();
+ $categories = array();
+ foreach ($cas as $ca)
+ {
+ $categories[] = $ca->getCategoryId();
+ }
+
+ $criteria = new Criteria();
+ $criteria->add(JobeetJobPeer::CATEGORY_ID, $categories, Criteria::IN);
+ JobeetJobPeer::addActiveJobsCriteria($criteria);
+
+ return JobeetJobPeer::doSelect($criteria);
+ }
+
+ // ...
+ }
+</propel>
+<doctrine>
+ [php]
+ // 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();
+ }
+
+ // ...
+ }
+</doctrine>
+
+Последним шагом является создание действия `api` и шаблонов. Создайте модуль
+задачей `generate:module`:
+
+ $ php symfony generate:module frontend api
+
+>**NOTE**
+>Так как мы не будем использовать действие `index`, вы можете его убрать из
+>контроллера. Также удалите шаблон `indexSuccess.php`.
+
+### Действие
+
+Все форматы используют одно и то же действие `list`:
+
+ [php]
+ // 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`, мы передаем массив
строк.
+Так как у нас есть три разных шаблона для одного действия, логика обработки
значений
+была вынесена в метод `JobeetJob::asArray()`:
+
+ [php]
+<propel>
+ // lib/model/JobeetJob.php
+</propel>
+<doctrine>
+ // lib/model/doctrine/JobeetJob.class.php
+</doctrine>
+ 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(),
+<propel>
+ 'expires_at' => $this->getCreatedAt('c'),
+</propel>
+<doctrine>
+ 'expires_at' => $this->getCreatedAt(),
+</doctrine>
+ );
+ }
+
+ // ...
+ }
+
+### Формат `xml`
+
+Добавление поддержки формата `xml` просто настолько же, насколько просто
создание шаблона:
+
+ [php]
+ <!-- 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](http://json.org/):
+
+ [php]
+ <!-- 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`
+
+Для встроеных форматов некоторые настройки, Symfony выполняет неявно, такие как
+изменение типа содержимого, или отключение декоратора (layout).
+
+Поскольку YAML формат не находится в этом списке, необходимо изменить
+тип содержимого в ответе и отключить layout:
+
+ [php]
+ 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()` изменяет layout по умолчанию, или отключает
его,
+параметром `false`.
+
+Шаблон для YAML:
+
+ [php]
+ <!-- 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; ?>
+
+Если Вы попытаетесь вызвать веб-сервис, указав неверный токен, вы получите xml
страницу
+с ошибкой 404, а также страницу 404 json, для json формата. Но Symfony не
знает,
+что отображать для YAML формата.
+
+Каждый раз, когда Вы создаете формат, должен быть создан пользовательский
шаблон
+сообщения об ошибке. Этот шаблон будет использован для страниц ошибки 404 и
других исключений.
+
+Так как обработка исключений должна быть разной в промышленной среде и в среде
разработки,
+нужно создать два файла (`config/error/exception.yaml.php`
+для среды разработки, and `config/error/error.yaml.php` для промышленной
среды):
+
+ [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,
+ ))) ?>
+
+Перед тем, как испробовать это, Вы должны создать layout для формата YAML:
+
+ [php]
+ // apps/frontend/templates/layout.yaml.php
+ <?php echo $sf_content ?>
+
+
+
+>**TIP**
+>**TIP**
+>Переопределение ошибки 404 и шаблонов обработки исключений для встроенных
шаблонов
+>так же просто, как просто создание файла в папке `config/error/`
+
+Тестирование веб-сервисов
+-------------------------
+
+Для тестирования веб-сервисов, скопируйте соответствующий файл данных из
`data/fixtures/`
+в `test/fixtures/` и замените содержание файла `apiActionsTest.php`, который
был
+сгенерирован автоматически, следующим кодом:
+
+ [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()
+ ;
+
+В этом тесте вы увидите два новых метода:
+
+ * `isFormat()`: Проверяет формат запроса
+ * `contains()`: Для не HTML формата, проверяет содержит ли ответ ожидаемый
+ текст.
+
+Форма для регистрации партнеров
+-------------------------------
+
+Теперь когда веб-сервисы готовы к использованию, давайте создадим форму для
+регистрации партнеров. Мы ещё раз опишем стандартный процесс добавления нового
+функционала в приложение.
+
+### Маршрутизация
+
+Вы угадали. Первым делом мы добавим маршруты:
+
+ [yml]
+ # apps/frontend/config/routing.yml
+ affiliate:
+ class: sfPropelRouteCollection
+ options:
+ model: JobeetAffiliate
+ actions: [new, create]
+ object_actions: { wait: get }
+
+Это обычная ##ORM## коллекция маршрутов с новым конфигурационным параметром:
+`actions`. Так как нам не нужны все семь действий, генерируемых маршрутом по
умолчанию,
+параметр `actions` укажет маршрутизатору, соответствовать только действиям
`new` и `create`.
+Дополнительный маршрут `wait` будет использован для обратной связи с будущим
партнером.
+
+### Генерация модуля
+
+Второй стандартный шаг - это генерация модуля:
+
+ $ php symfony propel:generate-module frontend affiliate JobeetAffiliate
--non-verbose-templates
+
+### Шаблоны
+
+Задача `propel:generate-module` генерирует стандартные семь действий и
соответствующие
+им шаблоны. Удалите все файлы в папке `templates/`, кроме `_form.php` и
`newSuccess.php`.
+Замените содержимое оставшихся файлов следующим кодом:
+
+ [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`:
+
+ [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`:
+
+ [php]
+ // 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`:
+
+ [php]
+ // apps/frontend/modules/affiliate/actions/actions.class.php
+ $this->redirect($this->generateUrl('affiliate_wait', $jobeet_affiliate));
+
+Действие `wait` простое, так как нам не надо передавать ничего в шаблон.
+
+ [php]
+ // apps/frontend/modules/affiliate/actions/actions.class.php
+ public function executeWait()
+ {
+ }
+
+Партнер не может выбирать свой токен, также он не может активировать свой
аккаунт.
+Откройте файл `JobeetAffiliateForm` для изменения формы:
+
+ [php]
+<propel>
+ // lib/form/JobeetAffiliateForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetAffiliateForm.class.php
+</doctrine>
+ class JobeetAffiliateForm extends BaseJobeetAffiliateForm
+ {
+ public function configure()
+ {
+<propel>
+ unset($this['is_active'], $this['token'], $this['created_at']);
+
$this->widgetSchema['jobeet_category_affiliate_list']->setOption('expanded',
true);
+
$this->widgetSchema['jobeet_category_affiliate_list']->setLabel('Categories');
+
+
$this->validatorSchema['jobeet_category_affiliate_list']->setOption('required',
true);
+</propel>
+<doctrine>
+ 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);
+</doctrine>
+
+ $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`.
+
+Так как почтовые адреса и URL-ы обычно бывают длиннее указанного по умолчанию
размера
+поля ввода, с помощью метода `setAttribute()`, мы можем указать HTML атрибуты,
+которые будут использоваться по умолчанию.
+
+
+
+### Тесты
+
+Завершающим шагом будет написание нескольких функциональных тестов.
+
+Замените созданные тесты для модуля `affiliate` следующим кодом:
+
+ [php]
+ // 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]',
+<propel>
+ 'jobeet_category_affiliate_list' =>
array($browser->getProgrammingCategory()->getId()),
+</propel>
+<doctrine>
+ 'jobeet_categories_list' =>
array(Doctrine::getTable('JobeetCategory')->findOneBySlug('programming')->getId()),
+</doctrine>
+ )))->
+ 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]',
+ )))->
+<propel>
+ with('form')->isError('jobeet_category_affiliate_list')
+</propel>
+<doctrine>
+ with('form')->isError('jobeet_categories_list')
+</doctrine>
+ ;
+
+<propel>
+Для эмуляции выбранных чекбоксов, передайте массив идентификаторов. Для
упрощения задачи,
+создайте новый метод `getProgrammingCategory()` в классе
`JobeetTestFunctional`:
+
+ [php]
+ // lib/test/JobeetTestFunctional.class.php
+ class JobeetTestFunctional extends sfTestFunctional
+ {
+ public function getProgrammingCategory()
+ {
+ $criteria = new Criteria();
+ $criteria->add(JobeetCategoryPeer::SLUG, 'programming');
+
+ return JobeetCategoryPeer::doSelectOne($criteria);
+ }
+
+ // ...
+ }
+
+Но так как у нас уже есть этот код в методе `getMostRecentProgrammingJob()`,
+пришло время для рефакторинга кода и создания метода `getForSlug()` в
`JobeetCategoryPeer`:
+
+ [php]
+ // lib/model/JobeetCategoryPeer.php
+ static public function getForSlug($slug)
+ {
+ $criteria = new Criteria();
+ $criteria->add(self::SLUG, $slug);
+
+ return self::doSelectOne($criteria);
+ }
+
+Затем замените два вхождения этого кода в `JobeetTestFunctional`.
+</propel>
+
+Админка для партнеров (backend)
+-------------------------------
+
+В приложении backend, должен быть создан модуль `affiliate` для партнеров,
+активированных администратором:
+
+ $ php symfony propel:generate-admin backend JobeetAffiliate
--module=affiliate
+
+Для доступа к только что созданному модулю, добавьте ссылку в главном меню и
укажите
+количество партнеров, которые ждут активации.
+
+ [php]
+ <!-- apps/backend/templates/layout.php -->
+ <li>
+<propel>
+ <a href="<?php echo url_for('@jobeet_affiliate') ?>">
+ Affiliates - <strong><?php echo
JobeetAffiliatePeer::countToBeActivated() ?></strong>
+ </a>
+</propel>
+<doctrine>
+ <a href="<?php echo url_for('@jobeet_affiliate_affiliate') ?>">
+ Affiliates - <strong><?php echo
Doctrine::getTable('JobeetAffiliate')->countToBeActivated() ?></strong>
+ </a>
+</doctrine>
+ </li>
+
+<propel>
+ // lib/model/JobeetAffiliatePeer.php
+ class JobeetAffiliatePeer extends BaseJobeetAffiliatePeer
+ {
+ static public function countToBeActivated()
+ {
+ $criteria = new Criteria();
+ $criteria->add(self::IS_ACTIVE, 0);
+
+ return self::doCount($criteria);
+ }
+</propel>
+<doctrine>
+ // 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();
+ }
+</doctrine>
+
+ // ...
+
+ }
+
+Так как единственные действия, необходимые в backend'е это активация и
деактивация,
+измените раздел `config` в генераторе по умолчанию, для упрощения интерфейса, и
+добавьте ссылку на активацию аккаунтов прямо из списка:
+
+ [yml]
+ # 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]
+
+Чтобы сделать работу администраторов более продуктивной, измените фильтры по
умолчанию
+таким образом, чтобы они отображали только тех партнеров, которых нужно
активировать:
+
+ [php]
+ //
apps/backend/modules/affiliate/lib/affiliateGeneratorConfiguration.class.php
+ class affiliateGeneratorConfiguration extends
BaseAffiliateGeneratorConfiguration
+ {
+ public function getFilterDefaults()
+ {
+ return array('is_active' => '0');
+ }
+ }
+
+Единственное, что осталось написать, это код для действий `activate` и
`deactivate`:
+
+ [php]
+ // apps/backend/modules/affiliate/actions/actions.class.php
+ class affiliateActions extends autoAffiliateActions
+ {
+ public function executeListActivate()
+ {
+ $this->getRoute()->getObject()->activate();
+
+<propel>
+ $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+ $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+ }
+
+ public function executeListDeactivate()
+ {
+ $this->getRoute()->getObject()->deactivate();
+
+<propel>
+ $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+ $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+ }
+
+ public function executeBatchActivate(sfWebRequest $request)
+ {
+<propel>
+ $affiliates =
JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));
+</propel>
+<doctrine>
+ $q = Doctrine_Query::create()
+ ->from('JobeetAffiliate a')
+ ->whereIn('a.id', $request->getParameter('ids'));
+
+ $affiliates = $q->execute();
+</doctrine>
+
+ foreach ($affiliates as $affiliate)
+ {
+ $affiliate->activate();
+ }
+
+<propel>
+ $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+ $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+ }
+
+ public function executeBatchDeactivate(sfWebRequest $request)
+ {
+<propel>
+ $affiliates =
JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));
+</propel>
+<doctrine>
+ $q = Doctrine_Query::create()
+ ->from('JobeetAffiliate a')
+ ->whereIn('a.id', $request->getParameter('ids'));
+
+ $affiliates = $q->execute();
+</doctrine>
+
+ foreach ($affiliates as $affiliate)
+ {
+ $affiliate->deactivate();
+ }
+
+<propel>
+ $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+ $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+ }
+ }
+
+<propel>
+ // lib/model/JobeetAffiliate.php
+</propel>
+<doctrine>
+ // lib/model/doctrine/JobeetAffiliate.class.php
+</doctrine>
+ class JobeetAffiliate extends BaseJobeetAffiliate
+ {
+ public function activate()
+ {
+ $this->setIsActive(true);
+
+ return $this->save();
+ }
+
+ public function deactivate()
+ {
+ $this->setIsActive(false);
+
+ return $this->save();
+ }
+
+ // ...
+ }
+
+
+
+Отправка электронной почты
+--------------------------
+
+Каждый раз, когда администратор активирует аккаунт партнера, должно высылаться
+письмо партнеру с токеном и ссылкой на подтверждение подписки.
+
+На PHP написано много хороших библиотек для отправки электронной почты,
например эти:
+[SwiftMailer](http://www.swiftmailer.org/),
+[Zend_Mail](http://framework.zend.com/), and
+[ezcMail](http://ezcomponents.org/docs/tutorials/Mail).
+Поскольку в следующих уроках мы будем использовать части из Zend Framework,
давайте
+воспользуемся `Zend Mail` для отправки нашей почты.
+
+### Установка и настройка Zend Framework
+
+Библиотека Zend Mail является частью Zend Framework. Так как нам не нужен весь
+Zend Framework, мы установим только необходимые части в директорию
`lib/vendor/`,
+по соседству с самим фреймворком Symfony.
+
+Сначала загрузите
+[Zend Framework](http://framework.zend.com/download/overview), затем
разархивируйте
+файлы в директорию `lib/vendor/Zend/`.
+
+>**NOTE**
+>Данные инструкции были опробованы в версии 1.8.0 Zend Framework.
+
+Вы можете очистить директорию, оставив только следующие файлы и директории:
+
+ * `Exception.php`
+ * `Loader/`
+ * `Loader.php`
+ * `Mail/`
+ * `Mail.php`
+ * `Mime/`
+ * `Mime.php`
+ * `Search/`
+
+>**NOTE**
+>Директория `Search/` не нужна для отправки писем, но она нам понадобится в
+>завтрашнем уроке.
+
+Затем добавьте следующий код в класс `ProjectConfiguration` для предоставления
+простого способа регистрации автозагрузчика Zend:
+
+ [php]
+ // 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/Autoloader.php';
+ Zend_Loader_Autoloader::getInstance();
+ self::$zendLoaded = true;
+ }
+
+ // ...
+ }
+
+### Отправка писем
+
+Измените действие `activate` для отправки письма, после того как администратор
+проверит партнера:
+
+ [php]
+ // apps/backend/modules/affiliate/actions/actions.class.php
+ class affiliateActions extends autoAffiliateActions
+ {
+ public function executeListActivate()
+ {
+ $affiliate = $this->getRoute()->getObject();
+ $affiliate->activate();
+
+ // send an email to the affiliate
+ 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();
+
+<propel>
+ $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+ $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+ }
+
+ // ...
+ }
+
+Для того, чтобы код заработал, Вам надо изменить `[email protected]` на
реальный
+почтовый адрес.
+
+>**NOTE**
+>Полноценное руководство по библиотеке `Zend_Mail` доступно на
+>[сайте Zend Framework](http://framework.zend.com/manual/en/zend.mail.html).
+
+Увидимся завтра!
+----------------
+
+Благодаря REST-архитектуре Symfony, реализация веб сервисов в Вашем проекте
+становится достаточно простой. Хотя мы написали код для веб-сервиса
+только для чтения данных, мы уже обладаем достаточными знаниями для написания
+веб-сервиса способного и читать, и записывать данные.
+
+Создание формы регистрации партнера в приложении frontend и его админки в
backend
+было достаточно просто, поскольку Вы уже знакомы с процедурой добавления новых
+возможностей в Ваш проект.
+
+Вспомните требования из дня 2:
+
+ "Партнер также может ограничить количество получаемых вакансии, и фильтровать
+ результат по категории."
+
+Решение этой задачи настолько просто, что мы дадим Вам выполнить её сегодня
самостоятельно.
+
+Завтра, мы завершим последнюю недостающую функциональность сайта Jobeet -
поисковый движок.
+
+__ORM__
--
You received this message because you are subscribed to the Google Groups
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/symfony-svn?hl=en.