From 4f7fac989f9b68dbd5eafa9cfdd0375b7fe7bdba Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Thu, 23 Sep 2010 13:07:00 +0400 Subject: [PATCH 01/14] 100% translated and tested --- .../05-Custom-Widgets-and-Validators.markdown | 293 ++++------ .../ru/06-Advanced-Forms.markdown | 543 ++++++------------ .../ru/A-sfWidgetFormGMapAddress.markdown | 37 +- 3 files changed, 289 insertions(+), 584 deletions(-) mode change 100755 => 100644 more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown mode change 100755 => 100644 more-with-symfony/ru/06-Advanced-Forms.markdown mode change 100755 => 100644 more-with-symfony/ru/A-sfWidgetFormGMapAddress.markdown diff --git a/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown b/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown old mode 100755 new mode 100644 index 7bdd8b6..a96112f --- a/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown +++ b/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown @@ -1,61 +1,44 @@ -Custom Widgets and Validators +Свои собственные виджеты и валидаторы ============================= -*by Thomas Rabaix* +*автор: Thomas Rabaix; перевод на русский — BRIGADA* -This chapter explains how to build a custom widget and validator for use -in the form framework. It will explain the internals of `sfWidgetForm` and -`sfValidator`, as well as how to build both a simple and complex widget. +Этот раздел объясняет, как создать свой собственный виджет и валидатор для использования в фреймворке форм. Здесь обсуждаются внутренности классов `sfWidgetForm` и `sfValidator`, а также описывается процесс построения простого и сложного виджета. -Widget and Validator Internals +Внутренности виджетов и валидаторов ------------------------------ -### `sfWidgetForm` Internals +### Внутренности `sfWidgetForm` -An object of the ~`sfWidgetForm`~ class represents the visual implementation of how -related data will be edited. A string value, for example, might be edited -with a simple text box or an advanced WYSIWYG editor. In order to be fully configurable, -the `sfWidgetForm` class has two important properties: `options` and `attributes`. +Объект класса ~`sfWidgetForm`~ представляет визуальную реализацию того, как соответствующие данные должны редактироваться. Строковое значение, например, может быть изменено в простом текстовом поле, а может — в расширенном WYSIWYG-редакторе. Для обеспечения полноценного конфигурирования, класс `sfWidgetForm` имеет два важных свойства: `options` и `attributes`. - * `options`: used to configure the widget (e.g. the database query to be - used when creating a list to be used in a select box) + * `options` — используется для конфигурирования виджета (например, задаётся запрос к базе данных для создания содержимого ниспадающего списка) - * `attributes`: HTML attributes added to the element upon rendering + * `attributes` — HTML-атрибуты, добавляемые к элементу при его выводе -Additionally, the `sfWidgetForm` class implements two important methods: +Дополнительно в классе `sfWidgetForm` реализовано два важных метода: - * `configure()`: defines which options are *optional* or *mandatory*. - While it is not a good practice to override the constructor, the `configure()` - method can be safely overridden. + * `configure()` — определяет, какие опции являются *необязательными*, а какие — *важными*. + Так как переопределение конструктора является не очень хорошей практикой, то лучше делать всю необходимую работу в методе `configure()` — это полностью безопасно. - * `render()`: outputs the HTML for the widget. The method has a mandatory - first argument, the HTML widget name, and an optional second argument, - the value. + * `render()` — выводит HTML-код виджета. У этого метода есть один важный первый аргумент, имя виджета, а также необязательный второй аргумент — значение. >**NOTE** ->An `sfWidgetForm` object does not know anything about its name or its value. ->The component is responsible only for rendering the widget. The name and ->the value are managed by an `sfFormFieldSchema` object, which is the link ->between the data and the widgets. +>Объект `sfWidgetForm` ничего не знает о своем имени и значении. Компонент отвечает только за вывод виджета. Именами и значениями управляет объект `sfFormFieldSchema`, который связывает данные с виджетами. -### sfValidatorBase Internals +### Внутренности sfValidatorBase -The ~`sfValidatorBase`~ class is the base class of each validator. The -~`sfValidatorBase::clean()`~ method is the most important method of this class -as it checks if the value is valid depending on the provided options. +Класс ~`sfValidatorBase`~ является базовым для любого валидатора. Метод ~`sfValidatorBase::clean()`~ — самый важный метод этого класса, так как он проверяет допустимость значения в соответствии с указанными опциями. -Internally, the `clean()` method perform several different actions: +Внутри метод `clean()` выполняет несколько действий: - * trims the input value for string values (if specified via the `trim` option) - * checks if the value is empty - * calls the validator's `doClean()` method. + * обрезает начальные и конечные пробелы в строковых значениях (если указана опция `trim`) + * проверяет значение на пустоту + * вызывает метод валидатора `doClean()`. -The `doClean()` method is the method which implements the main validation -logic. It is not good practice to override the `clean()` method. Instead, -always perform any custom logic via the `doClean()` method. +Метод `doClean()` определяет основную логику валидатора. Плохой практикой является переопределение метода `clean()`, свою логику работы валидатора следует реализовывать в методе `doClean()`. -A validator can also be used as a standalone component to check input integrity. -For instance, the `sfValidatorEmail` validator will check if the email is valid: +Валидатор также может быть использован как самостоятельный компонент, проверяющий целостность ввода. Например, валидатор `sfValidatorEmail` проверяет допустимость электронной почты: [php] $v = new sfValidatorEmail(); @@ -70,29 +53,21 @@ For instance, the `sfValidatorEmail` validator will check if the email is valid: } >**NOTE** ->When a form is bound to the request values, the `sfForm` object keeps ->references to the original (dirty) values and the validated (clean) values. ->The original values are used when the form is redrawn, while the cleaned ->values are used by the application (e.g. to save the object). +>Когда форма связывается со значениями запроса, объект `sfForm` сохраняет ссылки на оригинальные (грязные) и прошедшие валидатор (чистые) значения. Оригинальные значения используются при повторном выводе формы, а чистые используются приложением (например, при сохранении объекта). -### The `options` Attribute +### Атрибут `options` -Both the `sfWidgetForm` and `sfValidatorBase` objects have a variety of options: -some are optional while others are mandatory. These options are defined -inside each class's `configure()` method via: +Объекты `sfWidgetForm` и `sfValidatorBase` имеют различные опции: некоторые из них обязательны, а другие нет. Все эти опции определяются каждым классом в методе `configure()` через вызовы: - * `addOption($name, $value)`: defines an option with a name and a default value - * `addRequiredOption($name)`: defines a mandatory option + * `addOption($name, $value)` — определяет опцию с именем `name` и значением по умолчанию `value` + * `addRequiredOption($name)` — определяет обязательную опцию -These two methods are very convenient as they ensure that dependency values -are correctly passed to the validator or the widget. +Эти два метода очень удобны, поскольку гарантируют что зависимые значения корректно передаются в валидатор или виджет. -Building a Simple Widget and Validator +Построение простого виджета и валидатора -------------------------------------- -This section will explain how to build a simple widget. This particular widget -will be called a "Trilean" widget. The widget will display a select box with three choices: -`No`, `Yes` and `Null`. +Этот раздел описывает процесс построения простого виджета. Наш специальный виджет будет называться "Trilean". Он будет отображать поле выбора с тремя вариантами: `No`, `Yes` и `Null`. [php] class sfWidgetFormTrilean extends sfWidgetForm @@ -135,29 +110,21 @@ will be called a "Trilean" widget. The widget will display a select box with thr } } -The `configure()` method defines the option values list via the `choices` option. -This array can be redefined (i.e. to change the associated label of each value). -There is no limit to the number of option a widget can define. The base -widget class, however, declares a few standard options, which function like -de-facto reserved options: +Метод `configure()` определяет список значений через опцию `choices`. Этот массив может быть переопределён (например, для изменения выводимых меток и соответствующих им значений). Никаких ограничений на число опций виджета не определяется. Базовый класс виджета, однако, определяет ряд стандартных опций, которые работают как зарезервированные: - * `id_format`: the id format, default is '%s' + * `id_format` — формат идентификатора, по умолчанию '%s' - * `is_hidden`: boolean value to define if the widget is a hidden field (used - by `sfForm::renderHiddenFields()` to render all hidden fields at once) + * `is_hidden` — булево значение, определяющее видимость поля (используется `sfForm::renderHiddenFields()` для вывода всех скрытых полей разом) - * `needs_multipart`: boolean value to define if the form tag should include - the multipart option (i.e. for file uploads) + * `needs_multipart` — булево значение, определяющее необходимость включения в тэг form опции multipart (например, для загрузки файлов) - * `default`: The default value that should be used to render the widget - if no value is provided + * `default` — значение по умолчанию, которое используется при выводе виджета без заданного значения - * `label`: The default widget label + * `label` — метка виджета по умолчанию -The `render()` method generates the corresponding HTML for a select box. The -method calls the built-in `renderContentTag()` function to help render HTML tags. +Метод `render()` генерирует соответствующий полю выбора HTML-код. Для этого он вызывает встроенную функцию `renderContentTag()`. -The widget is now complete. Let's create the corresponding validator: +Теперь наш виджет готов, давайте создадим соответствующий валидатор: [php] class sfValidatorTrilean extends sfValidatorBase @@ -195,91 +162,64 @@ The widget is now complete. Let's create the corresponding validator: } } -The `sfValidatorTrilean` validator defines three options in the `configure()` -method. Each option is a set of valid values. As these are defined as options, -the developer can customize the values depending on the specification. +Валидатор `sfValidatorTrilean` в методе `configure()` определяет три опции. Каждая из этих опций является набором допустимых значений. Так как они определены в опциях, разработчик может изменить их в соответствии со своими нуждами. -The `doClean()` method checks if the value matches a set a valid values and -returns the cleaned value. If no value is matched, the method will raise an -`sfValidatorError` which is the standard validation error in the form framework. +Метод `doClean()` проверяет соответствие переданного значения наборам допустимых и возвращает очищенное значение. Если соответствия значения не обнаружено, генерируется исключение `sfValidatorError`, являющееся стандартной ошибкой валидации в фреймворке форм. -The last method, `isEmpty()`, is overridden as the default behavior of this -method is to return `true` if `null` is provided. As the current widget allows -`null` as a valid value, the method must always return `false`. +Последний метод, `isEmpty()`, переопределён, так как его поведение по умолчанию должно вернуть `true` если было передано значение `null`. А наш виджет считает `null` допустимым значением, поэтому этот метод должен всегда возвращать `false`. >**Note**: -> If `isEmpty()` returns true, the `doClean()` method will never be called. +> Если бы `isEmpty()` возвращал `true`, то метод `doClean()` никогда не был бы вызван. -While this widget was fairly straightforward, it introduced some important base features -that will be needed as we go further. The next section will create a more -complex widget with multiple fields and JavaScript interaction. +Несмотря на то, что разработанный нами виджет был достаточно простым, он позволил нам продемонстрировать некоторые важные особенности, которые потребуются в дальнейшем. В следующем разделе мы создадим более сложный виджет с несколькими полями и использованием JavaScript. -The Google Address Map Widget +Виджет Google Address Map ----------------------------- -In this section, we are going to build a complex widget. New methods will -be introduced and the widget will have some JavaScript interaction as well. -The widget will be called "GMAW": "Google Map Address Widget". +В этом разделе, мы будем создавать сложный виджет. Будут введены новые методы, а также виджет будет использовать JavaScript-взаимодействие. Мы назовём новый виджет "GMAW" — "Google Map Address Widget" (виджет адресов гугло-карт). -What do we want to achieve? The widget should provide an easy way for the -end user to add an address. By using an input text field and with google's -map services we can achieve this goal. +Чего мы хотим достичь? Виджет должен обеспечить конечному пользователю простой способ добавления адресов. С помощью поля ввода текста и услуг, предоставляемых сервисом карт Google, мы сможем достичь этой цели. -!["Google Map Address Widget" mashup](http://www.symfony-project.org/images/more-with-symfony/widgets-figure-01.png ""Google Map Address Widget" mashup") +![Набросок "Google Map Address Widget"](http://www.symfony-project.org/images/more-with-symfony/widgets-figure-01.png "Набросок "Google Map Address Widget"") -Use case 1: +Вариант использования 1: - * The user types an address. - * The user clicks the "lookup" button. - * The latitude and longitude hidden fields are updated and a new marker - is created on the map. The marker is positioned at the location of the - address. If the Google Geocoding service cannot find the address an error - message will popup. + * Пользователь вводит адрес. + * Пользователь щёлкает кнопку "поиск" (lookup). + * Скрытые поля, хранящие значения широты и долготы обновляются, и на карте создаётся новый маркер. Маркер указывает на положение введённого адреса. Если сервис геолокации Google не может найти адрес, появляется сообщение об ошибке. -Use case 2: +Вариант использования 2: - * The user clicks on the map. - * The latitude and longitude hidden fields are updated. - * Reverse lookup is used to find the address. + * Пользователь кликает мышкой по карте. + * Скрытые поля, хранящие значения широты и долготы обновляются. + * Для поиска соответствующего адреса используется обратный запрос. -*The following fields need to be posted and handled by the form:* +*Следующие поля должны выводиться и обрабатываться формой:* - * `latitude`: float, between 90 and -90 - * `longitude`: float, between 180 and -180 - * `address`: string, plain text only + * `latitude` — число с плавающей точкой (float), в диапазоне от 90 до -90 + * `longitude` — число с плавающей точкой (float), в диапазоне от 180 до -180 + * `address` — строка (string), только простой текст -The widget's functional specifications have just been defined, now let's -define the technical tools and their scopes: +Функциональная спецификация виджета была только что дана, теперь давайте определим технические средства и области их применения: - * Google map and Geocoding services: displays the map and retrieves address information - * jQuery: adds JavaScript interactions between the form and the field - * sfForm: draws the widget and validates the inputs + * Карты Google и сервис геолокации (Geocoding): отображение карты и получение адресной информации + * jQuery: добавление JavaScript-взаимодействия между формой и полями + * sfForm: рисование виджета и валидация ввода -### `sfWidgetFormGMapAddress` Widget +### Виджет `sfWidgetFormGMapAddress` -As a widget is the visual representation of data, the `configure()` method -of the widget must have different options to tweak the Google map or modify -the styles of each element. One of the most important options is the -`template.html` option, which defines how all elements are ordered. -When building a widget it is very important to think about reusability and -extensibility. +Поскольку виджет — это визуальное представление данных, метод `configure()` виджета должен иметь различные опции для настройки карт Google или изменения стилей элементов. Одна из важнейших опций, `template.html`, определяет порядок элементов. Когда вы разрабатываете новый виджет, всегда помните о его повторном использовании и расширяемости. -Another important thing is the external assets definition. An `sfWidgetForm` -class can implement two specific methods: +Другая важная вещь — внешнее определение активов. Класс `sfWidgetForm` должен реализовывать два специальных метода: - * `getJavascripts()` must return an array of JavaScript files; + * `getJavascripts()` должен вернуть массив JavaScript-файлов; - * `getStylesheets()` must return an array of stylesheet files - (where the key is the path and the value the media name). + * `getStylesheets()` должен вернуть массив CSS-файлов + (где ключом будет путь, а значение — соответствующая величина для атрибута media). -The current widget only requires some JavaScript to work so no stylesheet is needed. -In this case, however, the widget will not handle the initialization of the -Google JavaScript, though the widget will make use of the Google geocoding -and map services. Instead, it will be the developer's responsibility -to include it on the page. The reason behind this is that Google's services -may be used by other elements on the page, and not only by the widget. +Нашему виджету для работы потребуется только некоторый JavaScript-код. В данном случае, однако, виджет не будет обрабатывать инициализацию Google JavaScript, хотя и будет использовать соответствующие сервисы геолокации и карт. Вместо этого, мы обяжем разработчика включить их прямо на странице. Причина такого поведения заключается в том, что сервисы Google могут взаимодействовать с другими элементами на странице, а не только с нашим виджетом. -Let's jump to the code: +Давайте переходить к коду: [php] class sfWidgetFormGMapAddress extends sfWidgetForm @@ -333,7 +273,7 @@ Let's jump to the code: public function render($name, $value = null, $attributes = array(), $errors = array()) { - // define main template variables + // определяем главные переменные шаблона $template_vars = array( '{div.id}' => $this->generateId($name), '{div.class}' => $this->getOption('div.class'), @@ -348,22 +288,22 @@ Let's jump to the code: '{input.longitude.id}' => $this->generateId($name.'[longitude]'), ); - // avoid any notice errors to invalid $value format + // для исключения уведомления об ошибках при неверном формате $value $value = !is_array($value) ? array() : $value; $value['address'] = isset($value['address']) ? $value['address'] : ''; $value['longitude'] = isset($value['longitude']) ? $value['longitude'] : ''; $value['latitude'] = isset($value['latitude']) ? $value['latitude'] : ''; - // define the address widget + // определяем виджет поля адреса $address = new sfWidgetFormInputText(array(), $this->getOption('address.options')); $template_vars['{input.search}'] = $address->render($name.'[address]', $value['address']); - // define the longitude and latitude fields + // определяем поля широты и долготы $hidden = new sfWidgetFormInputHidden; $template_vars['{input.longitude}'] = $hidden->render($name.'[longitude]', $value['longitude']); $template_vars['{input.latitude}'] = $hidden->render($name.'[latitude]', $value['latitude']); - // merge templates and variables + // объединяем шаблоны и переменные return strtr( $this->getOption('template.html').$this->getOption('template.javascript'), $template_vars @@ -371,27 +311,16 @@ Let's jump to the code: } } -The widget uses the `generateId()` method to generate the `id` of each element. -The `$name` variable is defined by the `sfFormFieldSchema`, so the `$name` -variable is composed of the name form, any nested widget schema names and -the name of the widget as defined in the form `configure()`. +Виджет использует метод `generateId()` для генерации атрибута `id` каждого элемента. Переменная `$name` определяется `sfFormFieldSchema` так, что составляется из имени формы, имён других схем виджетов и имени самого виджета, как показано в методе `configure()`. >**NOTE** ->For instance, if the form name is `user`, the nested schema name is `location` ->and the widget name is `address`, the final `name` will be `user[location][address]` ->and the `id` will be `user_location_address`. In other words, ->`$this->generateId($name.'[latitude]')` will generate a valid and unique ->`id` for the `latitude` field. +>Например, если имя формы есть `user`, имя вложенной схемы есть `location`, а имя виджета — `address`, то результирующее значение для атрибута `name` будет `user[location][address]`, а значение атрибута `id` соответственно будет `user_location_address`. Другими словами, строка `$this->generateId($name.'[latitude]')` генерирует допустимый и уникальный атрибут `id` для поля `latitude`. -The different element `id` attributes are quite important as there are passed -to the JavaScript block (via the `template.js` variable), so the JavaScript can -properly handle the different elements. +Различные атрибуты `id` элементов очень важны, т.к. они передаются в блок JavaScript (через переменные `template.js`), что позволяет JavaScript-коду корректно обрабатывать различные элементы. -The `render()` method also instantiates two inner widgets: an `sfWidgetFormInputText` -widget, which is used to render the address field, and an `sfWidgetFormInputHidden` -widget, which is used to render the hidden fields. +Метод `render()` также выводит два типа встроенных виджетов: `sfWidgetFormInpuМеtText`, который используется как поле ввода адреса, и `sfWidgetFormInputHidden`, используемый для хранения скрытых полей с широтой и долготой. -The widget can be quickly tested with this small piece of code: +Виджет можно быстро протестировать следующим кодом: [php] $widget = new sfWidgetFormGMapAddress(); @@ -401,7 +330,7 @@ The widget can be quickly tested with this small piece of code: 'latitude' => '48.858205' )); -The output result is: +В результате будет выведено: [html]
@@ -424,36 +353,23 @@ The output result is: }) -The JavaScript part of the widget takes the different `id` attributes and -binds jQuery listeners to them so that certain JavaScript is triggered -when actions are performed. The JavaScript updates the hidden fields with -the longitude and latitude provided by the google geocoding service. +Часть виджета, реализованная на JavaScript, получает атрибуты `id` некоторых элементов и связывает их с библиотекой jQuery таким образом, что при обработке действий над этими элементами вызывается определённый JavaScript-код. Этот код обновляет скрытые поля со значениями долготы и широты в соответствии с ответом сервиса геолокации Google. -The JavaScript object has a few interesting methods: +JavaScript-код содержит ряд интересных функций: - * `init()`: the method where all variables are initialized and events - bound to different inputs + * `init()` — метод, в котором инициализируются переменные, а также происходит связывание полей с событиями - * `lookupCallback()`: a *static* method used by the geocoder method to - lookup the address provided by the user + * `lookupCallback()` — *статический* метод, используемый сервисом геолокации для поиска введённого пользователем адреса - * `reverseLookupCallback()`: is another *static* method used by the geocoder - to convert the given longitude and latitude into a valid address. + * `reverseLookupCallback()` — другой *статический* метод, используемый сервисом геологации для преобразования указанной долготы и широты в адрес. -The final JavaScript code can be viewed in Appendix A. +Готовый JavaScript-код можно посмотреть в Приложении A. -Please refer to the Google map documentation for more details on the functionality -of the Google maps [API](http://code.google.com/apis/maps/). +Пожалуйста, для получения дополнительной информации по использованию карт Google, ознакомьтесь с официальной [документацией](http://code.google.com/apis/maps/). -### `sfValidatorGMapAddress` Validator +### Валидатор `sfValidatorGMapAddress` -The `sfValidatorGMapAddress` class extends `sfValidatorBase` which already -performs one validation: specifically, if the field is set as required then -the value cannot be `null`. Thus, `sfValidatorGMapAddress` need only validate -the different values: `latitude`, `longitude` and `address`. The `$value` variable -should be an array, but as the user input should not be trusted, the validator -checks for the presence of all keys so that the inner validators are passed -valid values. +Класс `sfValidatorGMapAddress` наследуется от `sfValidatorBase`, который уже выполняет одну проверку: если для поля установлена опция `required`, то значение не может быть `null`. Таким образом, классу `sfValidatorGMapAddress` необходимо поверить сами значения `latitude`, `longitude` и `address`. Переменная `$value` должна быть массивом, но так как пользовательскому вводу доверять нельзя, валидатор проверяет присутствие всех ключей, и в результате внутренним валидаторам передаются допустимые значения. [php] class sfValidatorGMapAddress extends sfValidatorBase @@ -486,22 +402,13 @@ valid values. } >**NOTE** ->A validator always raises an `sfValidatorError` exception when a value is not ->valid. That's why the validation is surrounded by a `try/catch` block. ->In this validator, the validator re-throws a new `invalid` exception, which ->equates to an `invalid` validation error on the `sfValidatorGMapAddress` ->validator. +>Валидатор всегда генерирует исключение `sfValidatorError` при получении недопустимого значения. Именно из-за этого внутренний процесс валидации заключён в блок `try/catch`. В нашем валидаторе, при возникновении исключения в блоке `try` оно переповторяется как новое исключение `invalid`. -### Testing +### Тестирование -Why is testing important? The validator is the glue between the user input -and the application. If the validator is flawed, the application is vulnerable. -Fortunately, symfony comes with `lime` which is a testing library that is -very easy to use. +Почему тестирование столь важно? Валидатор является связующим звеном между пользовательским вводом и приложением. Если валидатор реализован плохо, то всё приложение потенциально уязвимо. Однако symfony включает очень простую в использовании библиотеку тестирования `lime`. -How can we test the validator? As stated before, a validator raises an exception -on a validation error. The test can send valid and invalid values to the validator -and check to see that the exception is thrown in the correct circumstances. +Как мы можем протестировать валидатор? Как было рассказано выше, валидатор вызывает исключение при возникновении ошибок валидации. Тест может послать допустимые и недопустимые значения, чтобы увидеть возникновение исключения в правильных обстоятельствах. [php] $t = new lime_test(7, new lime_output_color()); @@ -537,15 +444,9 @@ and check to see that the exception is thrown in the correct circumstances. $t->ok($validity != $catched, '::clean() '.$message); } -When the `sfForm::bind()` method is called, the form executes the `clean()` -method of each validator. This test reproduces this behavior by instantiating -the `sfValidatorGMapAddress` validator directly and testing different values. +Когда вызывается метод `sfForm::bind()`, форма выполняет метод `clean()` каждого валидатора. Этот тест воспроизводит такое поведение, путём непосредственного создания валидатора `sfValidatorGMapAddress` и тестирования в нём различных значений. -Final Thoughts +Заключение -------------- -The most common mistake when creating a widget is to be overly focused on -how the information will be stored in the database. The form framework is -simply a data container and validation framework. Therefore, a widget must -only manage its related information. If the data is valid then the different -cleaned values can then be used by the model or in the controller. +Наиболее распространенная ошибка при создании виджета заключается в том, что чрезмерно много внимания уделяется вопросам хранения информации в базе данных. Фреймворк форм — это просто контейнер данных и среда валидации. Поэтому виджет должен управлять только своей собственной информацией. Если данные допустимы, то различные очищенные значения могут использоваться в модели или контроллере. \ No newline at end of file diff --git a/more-with-symfony/ru/06-Advanced-Forms.markdown b/more-with-symfony/ru/06-Advanced-Forms.markdown old mode 100755 new mode 100644 index a151ef3..40396b9 --- a/more-with-symfony/ru/06-Advanced-Forms.markdown +++ b/more-with-symfony/ru/06-Advanced-Forms.markdown @@ -1,27 +1,16 @@ -Advanced Forms +Продвинутые формы ============== -*by Ryan Weaver, Fabien Potencier* +*авторы: Ryan Weaver и Fabien Potencier; перевод на русский - BRIGADA* -Symfony's form framework equips the developer with the tools necessary to -easily render and validate form data in an object-oriented matter. Thanks -to the ~`sfFormDoctrine`~ and ~`sfFormPropel`~ classes offered by each ORM, -the form framework can easily render and save forms that relate closely to -the data layer. +Среда обработки форм symfony снабжает разработчика инструментами, позволяющими легко выводить формы и проверять введённые в них данные. Благодаря имеющимся в каждой из ORM-систем классам ~`sfFormDoctrine`~ и ~`sfFormPropel`~, среда обработки форми позволяет легко выводить и сохранять формы, которые очень близки уровню данных. -Real-world situations, however, often require the developer to customize and -extend forms. In this chapter we'll present and solve several common, but -challenging form problems. We'll also dissect the ~`sfForm`~ object and -remove some of its mystery. +В реальных ситуациях зачастую требуется, чтобы разработчик имел возможность настраивать и расширять формы. В этой главе мы продемонстрируем решение нескольких общих проблем с формами. Также мы проанализируем объект ~`sfForm`~ и сбросим с него пелену загадочности. -Mini-Project: Products & Photos +Мини-проект: Продукты и Фотографии (Products & Photos) ------------------------------- -The first problem revolves around editing an individual product and an -unlimited number of photos for that product. The user must be able to edit -both the Product and the Product's Photos on the same form. We'll also need to -allow the user to upload up to two new Product Photos at a time. Here is a -possible schema: +Первая проблема заключается в редактировании одного продукта и неограниченного числа фотографий это продукта. Пользователю может потребоваться возможность редактировать и описание продукта и его фотографии на одной и той же странице. Мы также позволим пользователю загружать до двух новых изображений продукта одновременно. Вот возможная схема: [yml] Product: @@ -41,40 +30,26 @@ possible schema: foreignAlias: Photos onDelete: cascade -When completed, our form will look something like this: +Когда мы закончим, наша форма будет выглядеть приблизительно так: -![Product and photo form](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_01.png "Product form with embedded ProductPhoto forms") +![Product and photo form](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_01.png "Форма Product с внедрёнными формами ProductPhoto") -Learn more by doing the Examples +Изучайте больше, делая примеры -------------------------------- -The best way to learn advanced techniques is to follow along and test the -examples step by step. Thanks to the `--installer` feature of -[symfony](#chapter_03), we provide a simple way for you to create a working -project with a ready to be used SQLite database, the Doctrine database schema, -some fixtures, a `frontend` application, and a `product` module to work with. -Download the installer -[script](http://www.symfony-project.org/images/more-with-symfony/advanced_form_installer.php.src) -and run the following command to create the symfony project: +Лучший путь изучения продвинутых методик разработки заключается в последовательном изучении примеров и выполнении тестов. Благодаря встроенному в [symfony](#chapter_03) параметру командной строки `--installer`, мы предлагаем вам простой способ создания работающего проекта, с готовой к использованию базой данных SQLite, схемой базы данных Doctrine, некоторыми начальными данными, приложением `frontend` и модулем `product`. Для создания проекта symfony скачайте установочный [скрипт](http://www.symfony-project.org/images/more-with-symfony/advanced_form_installer.php.src) и выполните следующую команду: $ php symfony generate:project advanced_form --installer=/path/to/advanced_form_installer.php -This command creates a fully-working project with the database schema we have -introduced in the previous section. +Эта команда создаёт полностью работающий проект со схемой базы данных, которую мы привели в предыдущем разделе. >**NOTE** ->In this chapter, the file paths are for a symfony project running with ->Doctrine as generated by the previous task. +>В этой главе файловые пути соответствуют проекту symfony, полученному в предыдущей задаче. -Basic Form Setup +Начальная настройка формы ---------------- -Because the requirements involve changes to two different models (`Product` -and `ProductPhoto`), the solution will need to incorporate two different -symfony forms (`ProductForm` and `ProductPhotoForm`). Fortunately, the form -framework can easily combine multiple forms into one via -~`sfForm::embedForm()`~. First, setup the `ProductPhotoForm` independently. In -this example, let's use the `filename` field as a file upload field: +Поскольку требования задают нам две различные модели (`Product` и `ProductPhoto`), решение должно будет включить две различные формы (`ProductForm` и `ProductPhotoForm`). К счастью, фреймворк форм позволяет легко комбинировать несколько форм в одну через вызов ~`sfForm::embedForm()`~. Сначала настроим ProductPhotoForm. В этом примере будем использовать поле `filename` в качестве поля загрузки файла: [php] // lib/form/doctrine/ProductPhotoForm.class.php @@ -89,29 +64,20 @@ this example, let's use the `filename` field as a file upload field: ))); } -For this form, both the `caption` and `filename` fields are automatically -required, but for different reasons. The `caption` field is required because -the related column in the database schema has been defined with a `notnull` -property set to `true`. The `filename` field is required by default -because a validator object defaults to `true` for the `required` option. +Для этой формы поля `caption` и `filename` автоматически создаются «обязательными», но по разным причинам. Поле `caption` является обязательным, потому что соответствующая колонка в схеме базы данных содержит свойство `notnull` со значением `true`. Поле `filename` является обязательным, потому что объект валидатора использует значение `true` для параметра `required`. >**NOTE** ->~`sfForm::useFields()`~ is a new function to symfony 1.3 that allows the ->developer to specify exactly which fields the form should use and in which ->order they should be displayed. All other non-hidden fields are removed from ->the form. +>В symfony 1.3 появилась функция ~`sfForm::useFields()`~, которая позволяет +>разработчику указать те поля формы (а также их порядок), которые будут отображаться +>Все прочие не скрытые поля удаляются из формы. -So far we've done nothing more than ordinary form setup. Next, we'll combine -the forms into one. +На данный момент мы сделали начальную настройку формы. Далее мы объединим их в одну. -Embedding Forms +Встраивание форм --------------- -By using ~`sfForm::embedForm()`~, the independent `ProductForm` and -`ProductPhotoForms` can be combined with very little effort. The work is -always done in the *main* form, which in this case is `ProductForm`. The -requirements call for the ability to upload up to two product photos at once. -To accomplish this, embed two `ProductPhotoForm` objects into `ProductForm`: +Используя метод ~`sfForm::embedForm()`~ можно легко объединить независимые формы `ProductForm` и `ProductPhotoForms`. Эта работа всегда делается в *главной* форме, которой в нашем случае является `ProductForm`. В функциональных требованиях указано, что необходимо одновременно загружать две фотографии продукта. +Для реализации этого требования, встроим два объекта `ProductPhotoForm` в `ProductForm`: [php] // lib/form/doctrine/ProductForm.class.php @@ -130,34 +96,24 @@ To accomplish this, embed two `ProductPhotoForm` objects into `ProductForm`: $this->embedForm('newPhotos', $subForm); } -If you point your browser to the `product` module, you now have the ability to -upload two `ProductPhoto`s as well as modify the `Product` object itself. -Symfony automatically saves the new `ProductPhoto` objects and links them to -the corresponding `Product` object. Even the file upload, defined in -`ProductPhotoForm`, executes normally. +Если вы направите свой браузер на модуль `product`, то увидите что теперь можно загружать два экземпляра `ProductPhoto`, а также изменять сам объект `Product`. Symfony автоматически сохраняет новые объекты `ProductPhoto` и связывает их с соответствующим объектом `Product`. Даже загрузка файла, определённого в `ProductPhotoForm` происходит нормально. -Check that the records are saved correctly in the database: +Проверьте корректность сохранения записей в базе данных: $ php symfony doctrine:dql --table "FROM Product" $ php symfony doctrine:dql --table "FROM ProductPhoto" -In the `ProductPhoto` table, you will notice the filenames of the photos. -Everything is working as expected if you can find files with the same names as -the ones in the database in the `web/uploads/products/` directory. +В таблице `ProductPhoto` следует посмотреть имена файлов фотографий. Всё работает правильно, если те же самые имена вы видите в каталоге `web/uploads/products/`. >**NOTE** ->Because the filename and caption fields are required in `ProductPhotoForm`, ->validation of the main form will always fail unless the user is uploading ->two new photos. Keep reading to learn how to fix this problem. +>Так как поля с именем файла и описанием в таблице `ProductPhotoForm` описаны как обязательные, проверка главной формы всегда будет заканчиваться ошибкой, если пользователь не загружает две новые фотографии. Ниже мы покажем, как решить эту проблему. -Refactoring +Рефакторинг ----------- -Even if the previous form works as expected, it would be better to refactor -the code a bit to ease testing and to allow the code to be easily reused. +Даже если бы созданная в предыдущем разделе форма работала корректно, было бы неплохо сделать рефакторинг кода для упрощения тестирования и повторного использования. -First, let's create a new form that represents a collection of -`ProductPhotoForm`s, based on the code we have already written: +Во-первых, давайте на основе уже написанного кода создадим новую форму, представляющую собой коллекцию из `ProductPhotoForm`: [php] // lib/form/doctrine/ProductPhotoCollectionForm.class.php @@ -182,14 +138,13 @@ First, let's create a new form that represents a collection of } } -This form needs two options: +Эта форма требует указания двух опций: - * `product`: The product for which to create a collection of - `ProductPhotoForm`s; + * `product` - продукт, для которого создаётся коллекция объектов `ProductPhotoForm`; - * `size`: The number of `ProductPhotoForm`s to create (default to two). + * `size` - число создаваемых объектов `ProductPhotoForm` (по умолчанию два). -You can now change the configure method of `ProductForm` to read as follows: +Теперь вы можете изменить метод `configure` класса `ProductForm` следующим образом: [php] // lib/form/doctrine/ProductForm.class.php @@ -203,35 +158,23 @@ You can now change the configure method of `ProductForm` to read as follows: $this->embedForm('newPhotos', $form); } -Dissecting the sfForm Object +Препарирование объекта sfForm ---------------------------- -In the most basic sense, a web form is a collection of fields that are rendered -and submitted back to the server. In the same light, the ~`sfForm`~ object is -essentially an array of form *fields*. While ~`sfForm`~ manages the process, -the individual fields are responsible for defining how each will be rendered -and validated. +В самом базовом смысле, вэб-форма есть коллекция полей, которые вначале выводятся клиенту, а затем отсылаются обратно на сервер. В том же самом свете, объект ~`sfForm`~ в действительности есть массив *полей* формы. Сам объект ~`sfForm`~ управляет процессом, а содержащиеся в нём поля формы ответственны за определение того как именно они будут отображаться и проверяться. -In symfony, each form *field* is defined by two different objects: +В symfony, каждое *поле* формы определяется двумя разными объектами: - * A *widget* that outputs the form field's XHTML markup; + * *виджет* (widget), который выводит поле формы в разметке XHTML; - * A *validator* that cleans and validates the submitted field data. + * *валидатор* (validator), который осуществляет очистку и проверку присланных в поле данных. >**TIP** ->In symfony, a *widget* is defined as any object whose sole job is to ->output XHTML markup. While most commonly used with forms, a widget object ->could be created to output any markup. +>В symfony, *виджет* определён как любой объект, единственная задача которого заключается в выводе XHTML. Виджеты чаще всего используются в формах, однако такой объект можно создать для любого другого вывода разметки. -### A Form is an Array +### Форма есть массив -Recall that the ~`sfForm`~ object is "essentially an array of form *fields*." -To be more precise, `sfForm` houses both an array of widgets and an array -of validators for all of the fields of the form. These two arrays, called -`widgetSchema` and `validatorSchema` are properties of the `sfForm` class. -In order to add a field to a form, we simply add the field's widget to the -`widgetSchema` array and the field's validator to the `validatorSchema` array. -For example, the following code would add an `email` field to a form: +Напомним, объект ~`sfForm`~ есть «массив *полей* формы». Если быть точнее, ~`sfForm`~ содержит все поля формы в массивах виджетов и валидаторов. Эти два массива, называемые `widgetSchema` и `validatorSchema`, являются свойствами класса ~`sfForm`~. Для добавления поля к форме, мы просто добавляем соответствующий виджет в массив `widgetSchema`, а валидатор в массив `validatorSchema`. Например, следующий код добавляет к форме поле `email`: [php] public function configure() @@ -241,15 +184,12 @@ For example, the following code would add an `email` field to a form: } >**NOTE** ->The `widgetSchema` and `validatorSchema` arrays are actually special classes ->called ~`sfWidgetFormSchema`~ and ~`sfValidatorSchema`~ that implement the ->`ArrayAccess` interface. +>Массивы `widgetSchema` и `validatorSchema` в действительности являются специальными +>классами `sfWidgetFormSchema` и `sfValidatorSchema`, которые реализуют интерфейс `ArrayAccess`. -### Dissecting the `ProductForm` +### Препарирование `ProductForm` -As the `ProductForm` class ultimately extends `sfForm`, it too houses all of -its widgets and validators in `widgetSchema` and `validatorSchema` arrays. -Let's look at how each array is organized in the finished `ProductForm` object. +Поскольку класс `ProductForm` наследуется от класса `sfForm`, он также содержит все свои виджеты и валидаторы в массивах `widgetSchema` и `validatorSchema`. Давайте посмотрим, как организованы эти массивы в объекте `ProductForm`. [php] widgetSchema => array @@ -291,45 +231,29 @@ Let's look at how each array is organized in the finished `ProductForm` object. ) >**TIP** ->Just as `widgetSchema` and `validatorSchema` are actually objects that behave ->as arrays, the above arrays defined by the keys `newPhotos`, `0`, and `1` ->are also `sfWidgetSchema` and `sfValidatorSchema` objects. - -As expected, basic fields (`id`, `name` and `price`) are represented at the first -level of each array. In a form that embeds no other forms, both the `widgetSchema` -and `validatorSchema` arrays have just one level, representing the basic fields -on the form. The widgets and validators of any embedded forms are represented -as child arrays in `widgetSchema` and `validatorSchema` as seen above. The method -that manages this process is explained next. - -### Behind ~`sfForm::embedForm()`~ - -Keep in mind that a form is composed of an array of widgets and an array of validators. -Embedding one form into another essentially means that the widget and validator -arrays of one form are added to the widget and validator arrays of the main form. -This is entirely accomplished via `sfForm::embedForm()`. The result is always a -multi-dimensional addition to the `widgetSchema` and `validatorSchema` arrays as -seen above. - -Below, we'll discuss the setup of `ProductPhotoCollectionForm`, which binds -individual `ProductPhotoForm` objects into itself. This middle form acts -as a "wrapper" form and helps with overall form organization. Let's begin -with the following code from `ProductPhotoCollectionForm::configure()`: +>Так как `widgetSchema` и `validatorSchema` в действительности есть объекты, +>которые ведут себя как массивы, оба массива содержат ключ `newPhotos`, +>в котором `0` и `1` также объекты `sfWidgetSchema` и `sfValidatorSchema`. + +Как и ожидалось, основные поля (`id`, `name` и `price`) представлены на первом уровне каждого массива. В форме, в которую не внедрено других форм, массивы `widgetSchema` и `validatorSchema` имеют только один уровень, представляющий основные поля формы. Виджеты и валидаторы внедрённых форм представляются как дочерние массивы в `widgetSchema` и `validatorSchema` (что и видно выше). Метод, который управляет этим процессом, описывается ниже. + +### Метод ~`sfForm::embedForm()`~ + +Вы помните, что форма состоит из массива виджетов и массива валидаторов. Внедрение одной формы в другую означает, что массивы виджетов и валидаторов внедряемой формы добавляются к массивам виджетов и валидаторов главной формы. Это действие реализуется с помощью `sfForm::embedForm()`. В результате всегда происходит многомерное добавление к массивам `widgetSchema` и `validatorSchema`. + +Ниже мы рассмотрим настройку формы `ProductPhotoCollectionForm`, которая объединяет в себе отдельные объекты `ProductPhotoForm`. Эта промежуточная форма действует как «обёртка» и помогает более прозрачно организовывать формы. Давайте начнём со следующего кода в `ProductPhotoCollectionForm::configure()`: [php] $form = new ProductPhotoForm($productPhoto); $this->embedForm($i, $form); -The `ProductPhotoCollectionForm` form itself begins as a new `sfForm` object. -As such, its `widgetSchema` and `validatorSchema` arrays are empty. +Форма `ProductPhotoCollectionForm` изначально есть новый объект `sfForm`. Таким образом, массивы `widgetSchema` и `validatorSchema` пусты. [php] widgetSchema => array() validatorSchema => array() -Each `ProductPhotoForm`, however, is already prepared with three fields (`id`, `filename`, -and `caption`) and three corresponding items in its `widgetSchema` and `validatorSchema` -arrays. +Однако каждый экземпляр `ProductPhotoForm` уже содержит три поля (`id`, `filename` и `caption`), т.е. в массивах `widgetSchema` и `validatorSchema` есть соответствующие этим полям элементы. [php] widgetSchema => array @@ -346,13 +270,9 @@ arrays. [caption] => sfValidatorString, ) -The ~`sfForm::embedForm()`~ method simply adds the `widgetSchema` and `validatorSchema` -arrays from each `ProductPhotoForm` to the `widgetSchema` and `validatorSchema` -arrays of the empty `ProductPhotoCollectionForm` object. +Метод ~`sfForm::embedForm()`~ просто добавляет массивы `widgetSchema` и `validatorSchema` каждого экземпляра `ProductPhotoForm` к массивам `widgetSchema` и `validatorSchema` изначально пустого объекта `ProductPhotoCollectionForm`. -When finished, the `widgetSchema` and `validatorSchema arrays` of the wrapper -form (`ProductPhotoCollectionForm`) are multi-level arrays that hold the -widgets and validators from both `ProductPhotoForm`s. +В результате, массивы `widgetSchema` и `validatorSchema` формы-обёртки (`ProductPhotoCollectionForm`) будут многоуровневыми массивами, которые содержат виджеты и валидаторы из обоих `ProductPhotoForm`. [php] widgetSchema => array @@ -387,10 +307,7 @@ widgets and validators from both `ProductPhotoForm`s. ), ) -In the final step of our process, the resulting wrapper form, -`ProductPhotoCollectionForm`, is embedded directly into `ProductForm`. -This occurs inside `ProductForm::configure()`, which takes advantage of -all the work that was done inside `ProductPhotoCollectionForm`: +Последним шагом мы внедряем готовую форму-обёртку `ProductPhotoCollectionForm` напрямую в `ProductForm`. Это происходит в методе `ProductForm::configure()`: [php] $form = new ProductPhotoCollectionForm(null, array( @@ -400,19 +317,16 @@ all the work that was done inside `ProductPhotoCollectionForm`: $this->embedForm('newPhotos', $form); -This gives us the final `widgetSchema` and `validatorSchema` array structure -seen above. Notice that the `embedForm()` method is very similar to the simple -act of combining the `widgetSchema` and `validatorSchema` arrays manually: +Это позволяет нам получить массивы `widgetSchema` и `validatorSchema` с указанной выше структурой. Заметьте, что метод `embedForm()` действует подобно простому комбинированию массивов `widgetSchema` и `validatorSchema`: [php] $this->widgetSchema['newPhotos'] = $form->getWidgetSchema(); $this->validatorSchema['newPhotos'] = $form->getValidatorSchema(); -Rendering Embedded Forms in the View +Вывод внедрённых форм в Виде ------------------------------------ -The current `_form.php` template of the `product` module looks like the -following: +Текущий шаблон `_form.php` модуля `product` выглядит следующим образом: [php] // apps/frontend/module/product/templates/_form.php @@ -424,15 +338,9 @@ following: -The `` statement is the simplest way to display a form, -even the most complex ones. It is of great help when prototyping, but as soon -as you want to change the layout, you need to replace it with your own display -logic. Remove this line now as we will replace it in this section. +Выражение `` есть самый простой способ отображения даже сложных форм. Это очень помогает на этапе моделирования, однако когда вам потребуется изменить расположение элементов, вам нужно будет поменять этот код на собственную логику отображения. В этом разделе мы заменим эту строку на свой код. -The most important thing to understand when rendering embedded forms in the -view is the organization of the multi-level `widgetSchema` array explained in -the previous sections. For this example, let's begin by rendering the basic -`name` and `price` fields from the `ProductForm` in the view: +Наиболее важной вещью для понимания процесса вывода внедрённых форм является внутренняя организация многоуровневого массива `widgetSchema`. В нашем случае, давайте начнём с вывода в **Виде** полей `name` и `price` из `ProductForm`: [php] // apps/frontend/module/product/templates/_form.php @@ -442,68 +350,42 @@ the previous sections. For this example, let's begin by rendering the basic renderHiddenFields() ?> -As its name implies, the `renderHiddenFields()` renders all the hidden fields -of the form. +Как можно догадаться по имени метода, `renderHiddenFields()` выводит все скрытые поля формы. >**NOTE** ->The actions code was purposefully not shown here because it requires no special ->attention. Have a look at the `apps/frontend/modules/product/actions/actions.class.php` ->actions file. It looks like any normal CRUD and can be generated automatically via ->the `doctrine:generate-module` task. - -As we've already learned, the `sfForm` class houses the `widgetSchema` and -`validatorSchema` arrays that define our fields. Moreover, the `sfForm` class -implements the native PHP 5 `ArrayAccess` interface, meaning we can directly access -fields of the form by using the array key syntax seen above. - -To output the fields, you can simply access them directly and call the `renderRow()` -method. But what type of object is `$form['name']`? While you might expect -the answer to be the `sfWidgetFormInputText` widget for the `name` field, -the answer is actually something slightly different. - -### Rendering each Form Field with ~`sfFormField`~ - -By using the `widgetSchema` and `validatorSchema` arrays defined in each form class, -`sfForm` automatically generates a third array called `sfFormFieldSchema`. -This array contains a special object for each field that acts as a helper -class responsible for the field's output. The object, of type -~`sfFormField`~, is a combination of each field's widget and validator and is -automatically created. +>Мы специально не приводим здесь код для действий, т.к. он не требует никакого специального внимания. +>Посмотрите файл `apps/frontend/modules/product/actions/actions.class.php`. Он выглядит как любое нормальное +>CRUD-приложение (т.е. приложение, реализующее 4 основных действия с данными - создание, чтение, обновление и удаление) +>и может быть сгенерировано автоматически через задачу `doctrine:generate-module`. + +Как мы уже выяснили, класс `sfForm` содержит массивы `widgetSchema` и `validatorSchema`, которые определяют наши поля. Кроме того, класс `sfForm` реализует PHP5-интерфейс `ArrayAccess`, что позволяет нам обращаться к полям формы напрямую через ключи массива. + +Для вывода полей вы можете просто напрямую обратиться к ним и вызвать метод `renderRow()`. Но какого типа объект `$form['name']`? Вы могли бы сказать, что ответом будет виджет `sfWidgetFormInputText` для поля `name`, однако в действительности ответ несколько иной. + +### Вывод полей формы с помощью ~`sfFormField`~ + +Используя массивы `widgetSchema` и `validatorSchema`, определённые для каждого класса формы, `sfForm` автоматически генерирует третий массив `sfFormFieldSchema`. Этот массив содержит специальный объект для каждого поля, который действует как вспомогательный класс (хелпер), ответственный за вывод поля. Объект с типом `sfFormField` есть комбинация из соответствующего виджета и валидатора, который создаётся автоматически. [php] renderRow() ?> -In the above snippet, `$form['name']` is an `sfFormField` object, which houses -the `renderRow()` method along with several other useful rendering functions. +В приведённом выше коде, `$form['name']` является объектом `sfFormField`, который имеет метод `renderRow()`, а также другие полезные при выводе функции. -### sfFormField Rendering Methods +### Методы вывода sfFormField -Each `sfFormField` object can be used to easily render every aspect of the field -that it represents (e.g. the field itself, the label, error messages, etc.). -Some of the useful methods inside `sfFormField` include the following. Other -can be found via the [symfony 1.3 API](http://www.symfony-project.org/api/1_3/sfFormField). +Каждый объект `sfFormField` может быть использован для упрощения вывода любого связанного с полем аспекта (т.е. непосредственно поле, его метка, сообщения об ошибках и т.д.). Некоторые из методов `sfFormField` рассмотрены ниже. Остальные можно найти в документации [symfony 1.3 API](http://www.symfony-project.org/api/1_3/sfFormField). - * `sfFormField->render()`: Renders the form field (e.g. `input`, `select`) - with the correct value using the field's widget object. + * `sfFormField->render()` - выводит поле формы (например `input`, `select`) с корректным значением, используя виджет-объект. - * `sfFormField->renderError()`: Renders any validation errors on the field - using the field's validator object. + * `sfFormField->renderError()` - выводит все ошибки валидации поля, используя соответствующий валидатор-объект. - * `sfFormField->renderRow()`: All-encompassing: renders the label, the form - field, the error and the help message inside an XHTML markup wrapper. + * `sfFormField->renderRow()` - выводит метку, поле формы, ошибки и сообщение-подсказку в XHTML-обёртке. >**NOTE** ->In reality, each rendering function of the `sfFormField` class also uses information ->from the form's `widgetSchema` property (the `sfWidgetFormSchema` object that ->houses all of the widgets for the form). This class assists in the generation ->of each field's `name` and `id` attributes, keeps track of the label for each ->field, and defines the XHTML markup used with `renderRow()`. - -One important thing to note is that the `formFieldSchema` array always -mirrors the structure of the form's `widgetSchema` and `validatorSchema` arrays. -For example, the `formFieldSchema` array of the completed `ProductForm` -would have the following structure, which is the key to rendering each -field in the view: +>В действительности, все функции вывода класса `sfFormField` используют информацию из свойства `widgetSchema` формы (объект `sfWidgetFormSchema` хранит все виджеты формы). +>Этот класс помогает при генерации атрибутов `name` и `id` каждого поля, следит за метками полей и определяет используемую в `renderRow()` XHTML-разметку. + +Есть одна важная вещь, которую следует отметить: массив `formFieldSchema` всегда есть зеркальное отражение структуры массивов `widgetSchema` и `validatorSchema` формы. Например, массив `formFieldSchema` законченной формы `ProductForm` будет иметь следующую структуру, в которой выводимые в Виде поля являются ключами: [php] formFieldSchema => array @@ -525,10 +407,9 @@ field in the view: ), ) -### Rendering the New ProductForm +### Вывод новой формы ProductForm -Using the above array as our map, we can easily output the embedded `ProductPhotoForm` -fields in the view by locating and rendering the proper `sfFormField` objects: +Используя приведённый выше массив в качестве шпаргалки, мы можем очень легко вывести внедрённые в `ProductPhotoForm` поля, отыскивая и выводя соответствующие объекты `sfFormField`: [php] // apps/frontend/module/product/templates/_form.php @@ -537,66 +418,54 @@ fields in the view by locating and rendering the proper `sfFormField` objects: renderRow() ?> -The above block loops twice: once for the `0` form field array and -once for the `1` form field array. As seen in the above diagram, -the underlying objects of each array are `sfFormField` objects, which we can -output like any other fields. +Этот цикл выполняется дважды: один раз для поля формы с ключом `0` и один раз для поля с ключом `1`. Как видно в диаграмме выше, массивы хранят объекты `sfFormField`, которые мы можем вывести, как и другие поля. -Saving Object Forms +Сохранение объектов форм ------------------- -Under most circumstances, a form will relate directly to one or more database -tables and trigger changes to the data in those tables based on the submitted -values. Symfony automatically generates a form object for each schema model, -which extends either `sfFormDoctrine` or `sfFormPropel` depending on your -ORM. Each form class is similar and ultimately allows for submitted values to -be easily persisted in the database. +В большинстве случаев форма имеет прямое отношение к одной или нескольким таблицам базы данных и позволяет изменять данные в этих таблицах соответственно переданным значениям. Symfony автоматически генерирует объекты форм для каждой модели схемы данных, которые наследуются от `sfFormDoctrine` или `sfFormPropel` в зависимости от используемой вами ORM. Каждый класс формы соответствующим строкам в таблицах, что позволяет легко размещать их в базе данных. >**NOTE** ->~`sfFormObject`~ is a new class added in symfony 1.3 to handle all of the ->common tasks of `sfFormDoctrine` and `sfFormPropel`. Each class extends ->`sfFormObject`, which now manages part of the form-saving process described below. +>~`sfFormObject`~ - это новый класс, добавленный в symfony 1.3. Он обрабатывает все общие задачи `sfFormDoctrine` и `sfFormPropel`. Оба класса наследуются от `sfFormObject`, который теперь управляет описанным ниже процессом сохранения форм. -### The Form Saving Process +### Процесс сохранения форм -In our example, symfony automatically saves both the `Product` information and -new `ProductPhoto` objects without any additional effort by the developer. -The method that triggers the magic, ~`sfFormObject::save()`~, executes a variety -of methods behind the scenes. Understanding this process is key to extending -the process in more advanced situations. +В нашем примере symfony автоматически сохраняет информацию из объектов `Product` и `ProductPhoto` без каких-либо специальных усилий со стороны разработчика. Метод, который реализует это волшебство, называется ~`sfFormObject::save()`~, он в свою очередь вызывает множество других методов. Понимание этого процесса является ключом для перехода к более общим ситуациям. -The form saving process consists of a series of internally executed methods, -all of which happen after calling ~`sfFormObject::save()`~. The majority -of the work is wrapped in the ~`sfFormObject::updateObject()`~ method, which -is called recursively on all of your embedded forms. +Процесс сохранения формы состоит из серии внутренних вызовов методов, следующих за вызовом ~`sfFormObject::save()`~. Основная работа делается методом ~`sfFormObject::updateObject()`~, который рекурсивно вызывается для всех внедрённых форм. -![Form Saving Process](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_06.png "Detailed form saving process") +* _sfFormObject::save()_ + * _sfFormObject::doSave()_ + * _sfFormObject::updateObject()_ + 1. _sfFormDoctrine::processValues($values)_ + * Передаваемый в этот метод массив _$values_ есть ассоциативный массив "сырых" значений. + * Этот метод выполняет обработку значений верхнего уровня. + * Для каждого поля вызывается метод _updateXXXColumn()_, если он существует. + * Вызывается метод _sfFormDoctrine::processUploadedFile()_, который преобразует все значения из загруженных полей в объекты _sfValidatedFile_. + 2. _sfFormDoctrine::doUpdateObject($values)_ + * Массив _$values_ - это массив, возвращенный _processValues()_. + * Это метод просто обновляет объект в соответствии с массивом _$values_. + 3. _sfFormDoctrine::updateObjectEmbeddedForms($values)_ + * Вызывается метод _sfFormObject::updateObject()_ для каждой внедрённой формы. + * Описанный в шагах 1-3 процесс повторяется рекурсивно. >**NOTE** ->The majority of the saving process takes place from within the ~`sfFormObject::doSave()`~ ->method, which is called by `sfFormObject::save()` and wrapped in a database ->transaction. If you need to modify the saving process itself, `sfFormObject::doSave()` ->is usually the best place to do it. +>Основная часть процесса сохранения располагается в методе `sfFormObject::doSave()`, +>который вызывается `sfFormObject::save()` и обёрнут в транзакцию базы данных. +>Если вам необходимо изменить сам процесс сохранения, `sfFormObject::doSave()` +>обычно является наилучшим местом для этого. + -Ignoring Embedded Forms +Игнорирование внедрённых форм ----------------------- -The current `ProductForm` implementation has one major shortfall. Because the -`filename` and `caption` fields are required in `ProductPhotoForm`, validation -of the main form will always fail unless the user is uploading two new photos. -In other words, the user can't simply change the price of the `Product` without -also being required to upload two new photos. +У текущей реализации `ProductForm` есть один существенный недостаток. Так как поля `filename` и `caption` являются обязательными в `ProductPhotoForm`, валидация главной формы будет всегда завершаться ошибкой, пока пользователь не загрузит две новых фотографии. Другими словами, пользователь не сможет просто поменять цену в `Product` без загрузки пары новых фотографий. -![Product form failed photo validation](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_04.png "Product form fails validation on the photos") +![Форма Product с ошибками валидации](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_04.png "Форма Product не принята из-за ошибки валидации фотографий") -Let's redefine the requirements to include the following. If the user leaves -all the fields of a `ProductPhotoForm` blank, that form should be ignored -completely. However, if at least one field has data (i.e. `caption` or `filename`), -the form should validate and save normally. To accomplish this, we'll employ -an advanced technique involving the use of a custom post validator. +Давайте переопределим требования, чтобы включить следующее. Если пользователь оставляет все поля `ProductPhotoForm` пустыми, то эта форма должна быть проигнорирована. Однако, если любое из полей содержит данные (например `caption` или `filename`), форма должна проверяться и сохраняться. Для достижения этого, мы будем использовать специальную технику, основанную на реализации своих собственных валидаторов. -The first step, however, is to modify the `ProductPhotoForm` form to make the -`caption` and `filename` fields optional: +Но вначале необходимо изменить форму `ProductPhotoForm` так, чтобы сделать поля `caption` и `filename` необязательными: [php] // lib/form/doctrine/ProductPhotoForm.class.php @@ -611,11 +480,9 @@ The first step, however, is to modify the `ProductPhotoForm` form to make the $this->validatorSchema['caption']->setOption('required', false); } -In the above code, we have set the `required` option to `false` when -overriding the default validator for the `filename` field. Additionally, -we have explicitly set the `required` option of the `caption` field to `false`. +В приведённом выше коде для поля `filename` мы устанавливаем значение опции `required` в `false` и изменяем валидатор по умолчанию, а для поля `caption` только устанавливаем значение опции `required` в `false`. -Now, let's add the post validator to the `ProductPhotoCollectionForm`: +Теперь, давайте добавим пост-валидатор к `ProductPhotoCollectionForm`: [php] // lib/form/doctrine/ProductPhotoCollectionForm.class.php @@ -626,16 +493,11 @@ Now, let's add the post validator to the `ProductPhotoCollectionForm`: $this->mergePostValidator(new ProductPhotoValidatorSchema()); } -A post validator is a special type of validator that validates across all of -the submitted values (as opposed to validating the value of a single field). -One of the most common post validators is `sfValidatorSchemaCompare` which -verifies, for example, that one field is less than another field. +Пост-валидатор - это специальный тип валидатора, который проверяет корректность всех присланных значений одновременно (а не по отдельности для каждого поля). Один из наиболее часто используемых пост-валидаторов - `sfValidatorSchemaCompare`. Он позволяет проверить, например, что одно поле меньше другого. -### Creating a Custom Validator +### Создание своего собственного валидатора -Fortunately, creating a custom validator is actually quite easy. Create a -new file, `ProductPhotoValidatorSchema.class.php` and place it in the -`lib/validator/` directory (you'll need to create this directory): +К счастью, создание собственного валидатора в действительности достаточно простая задача. Создайте новый файл, `ProductPhotoValidatorSchema.class.php` и поместите его в каталог lib/validator/ (возможно, вам потребуется создать этот каталог): [php] // lib/validator/ProductPhotoValidatorSchema.class.php @@ -655,32 +517,32 @@ new file, `ProductPhotoValidatorSchema.class.php` and place it in the { $errorSchemaLocal = new sfValidatorErrorSchema($this); - // filename is filled but no caption + // поле filename заполнено, caption - нет if ($value['filename'] && !$value['caption']) { $errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'caption'); } - // caption is filled but no filename + // поле caption заполнено, filename - нет if ($value['caption'] && !$value['filename']) { $errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'filename'); } - // no caption and no filename, remove the empty values + // поля caption и filename не заполнены, удаляем пустые значения if (!$value['filename'] && !$value['caption']) { unset($values[$key]); } - // some error for this embedded-form + // в этой внедрённой форме есть некоторые ошибки if (count($errorSchemaLocal)) { $errorSchema->addError($errorSchemaLocal, (string) $key); } } - // throws the error for the main form + // передаём ошибку в главную форму if (count($errorSchema)) { throw new sfValidatorErrorSchema($this, $errorSchema); @@ -691,34 +553,22 @@ new file, `ProductPhotoValidatorSchema.class.php` and place it in the } >**TIP** ->All validators extend `sfValidatorBase` and require only the `doClean()` ->method. The `configure()` method can also be used to add options or messages to ->the validator. In this case, two messages are added to the validator. ->Similarly, additional options can be added via the `addOption()` method. +>Все валидаторы наследуются от `sfValidatorBase` и требуют реализации метода `doClean()`. +>Метод `configure()` может также использоваться, если необходимо добавить параметры или сообщения к валидатору. В данном случае, к валидатору добавляется два сообщения. +>Аналогично добавляются параметры при помощи метода `addOption()`. -The `doClean()` method is responsible for cleaning and validating the bound -values. The logic of the validator itself is quite simple: +Метод `doClean()` отвечает за очистку и проверку значений. Логика работы нашего валидатора весьма проста: - * If a photo is submitted with only the filename or a caption, we throw an - error (`sfValidatorErrorSchema`) with the appropriate message; + * Если фотография была отправлена с указанием только имени файла или описания, мы генерируем исключение (`sfValidatorErrorSchema`) с соответствующим сообщением; - * If a photo is submitted with no filename and no caption, we remove the - values altogether to avoid saving an empty photo; + * Если фотография была отправлена без имени файла и без описания, мы удаляем значения для предотвращения сохранения «пустой» фотографии; - * If no validation errors have occurred, the method returns the array of - cleaned values. + * Если никаких ошибок валидации не произошло, метод возвращает массив «очищенных» значений. >**TIP** ->Because the custom validator in this situation is meant to be used as a ->post validator, the `doClean()` method expects an array of the bound ->values and returns an array of cleaned values. Custom validators, however, ->can just as easily be created for individual fields. In that case, the ->`doClean()` method will expect just one value (the value of the submitted ->field) and will return just one value. +>Поскольку наш валидатор используется в качестве пост-валидатора, метод `doClean()` принимает ассоциативный массив значений и возвращает массив «очищенных» значений. Собственный валидатор также легко можно создать для конкретного поля. В этом случае, метод `doClean()` должен будет принимать одно значение (значение присланного поля) и должен будет вернуть только одно значение. -The last step is to override the `saveEmbeddedForms()` method of `ProductForm` -to remove empty photo forms to avoid saving an empty photo in the database (it -would otherwise throws an exception as the `caption` column is required): +Наконец, мы перепишем метод `saveEmbeddedForms()` формы `ProductForm` для предотвращения сохранения в базе данных пустых форм фотографий (в противном случае, будет вызвано исключение, т.к. поле `caption` является обязательным): [php] public function saveEmbeddedForms($con = null, $forms = null) @@ -739,17 +589,12 @@ would otherwise throws an exception as the `caption` column is required): return parent::saveEmbeddedForms($con, $forms); } -Easily Embedding Doctrine-Related Forms +Упрощённое внедрение Doctrine-форм --------------------------------------- -New to symfony 1.3 is the ~`sfFormDoctrine::embedRelation()`~ function which -allows the developer to embed n-to-many relationship into a form -automatically. Suppose, for example, that in addition to allowing the user to -upload two new `ProductPhotos`, we also want to allow the user to modify the -existing `ProductPhoto` objects related to this `Product`. +В symfony 1.3 появилась функция ~`sfFormDoctrine::embedRelation()`~, которая позволяет разработчику автоматически внедрять в форму взаимосвязи n-ко-многим. Предположим, например, что помимо возможности загрузки двух новых `ProductPhotos`, мы также хотим позволить пользователю изменять существующие объекты `ProductPhoto`, связанные с этим `Product`. -Next, use the `embedRelation()` method to add one additional -`ProductPhotoForm` object for each existing `ProductPhoto` object: +Используем метод `embedRelation()` для добавления одного дополнительного объекта `ProductPhotoForm` для каждого существующего объекта `ProductPhoto`: [php] // lib/form/doctrine/ProductForm.class.php @@ -760,10 +605,7 @@ Next, use the `embedRelation()` method to add one additional $this->embedRelation('Photos'); } -Internally, ~`sfFormDoctrine::embedRelation()`~ does almost exactly what we did -manually to embed our two new `ProductPhotoForm` objects. If two `ProductPhoto` -relations exist already, then the resulting `widgetSchema` and `validatorSchema` -of our form would take the following shape: +Внутри `sfFormDoctrine::embedRelation()` делает почти тоже самое, что мы уже делали руками для внедрения двух новых объектов `ProductPhotoForm`. Если две фотографии `ProductPhoto` уже существуют, тогда массивы `widgetSchema` и `validatorSchema` нашей формы выглядят следующим образом: [php] widgetSchema => array @@ -802,10 +644,9 @@ of our form would take the following shape: ), ) -![Product form with 2 existing photos](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_03.png "Product form with 2 existing photos") +![Форма Product с 2 существующими фотографиями](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_03.png "Форма Product с двумя существующими фотографиями") -The next step is to add code to the view that will render the new embedded -*Photo* forms: +Следующим шагом станет добавление к Виду кода, который выведет новые внедрённые формы *Photo*: [php] // apps/frontend/module/product/templates/_form.php @@ -814,11 +655,9 @@ The next step is to add code to the view that will render the new embedded renderRow(array('width' => 100)) ?> -This snippet is exactly the one we used earlier to embed the new photo forms. +Эта часть кода аналогична уже использованному нами при внедрении форм новых фотографий. -The last step is to convert the file upload field by one which allows the user -to see the current photo and to change it by a new one -(`sfWidgetFormInputFileEditable`): +Последний шаг заключается в изменении поля загруженного файла таким образом, чтобы позволить пользователю смотреть текущую фотографию и иметь возможность редактировать новые (`sfWidgetFormInputFileEditable`): [php] public function configure() @@ -841,27 +680,21 @@ to see the current photo and to change it by a new one $this->validatorSchema['caption']->setOption('required', false); } -Form Events +События формы ----------- -New to symfony 1.3 are form events that can be used to extend any form -object from anywhere in the project. Symfony exposes the following four form -events: +В symfony 1.3 появились события форм, которые можно использовать для расширения любой формы, откуда угодно в проекте. Symfony предоставляет следующие четыре события: - * `form.post_configure`: This event is notified after every form is configured - * `form.filter_values`: This event filters the merged, tainted parameters and files arrays just prior to binding - * `form.validation_error`: This event is notified whenever form validation fails - * `form.method_not_found`: This event is notified whenever an unknown method is called + * `form.post_configure` - это событие происходит после конфигурирования формы + * `form.filter_values` - это событие происходит при фильтрации непосредственно перед ассоциированием + * `form.validation_error` - это событие происходит, когда валидация формы завершается ошибкой + * `form.method_not_found` - это событие происходит, когда делается попытка вызова несуществующего метода -### Custom Logging via `form.validation_error` +### Организация логирования через `form.validation_error` -Using the form events, it's possible to add custom logging for validation -errors on any form in your project. This might be useful if you want to track -which forms and fields are causing confusion for your users. +При помощи событий формы можно реализовать логирование ошибок валидации для любой формы в вашем проекте. Это может быть полезно, если вы хотите проследить за тем, какие формы и поля вызывают затруднения у ваших пользователей. -Begin by registering a listener with the event dispatcher for the -`form.validation_error` event. Add the following to the `setup()` method -of `ProjectConfiguration`, which is located inside the `config` directory: +Начнём с регистрации "слушателя" диспетчера событий на событие `form.validation_error`. Добавьте следующий код в метод `setup()` класса `ProjectConfiguration`, который находится в каталоге `config`: [php] public function setup() @@ -874,10 +707,7 @@ of `ProjectConfiguration`, which is located inside the `config` directory: ); } -`BaseForm`, located in `lib/form`, is a special form class that all form -classes extend. Essentially, `BaseForm` is a class where code can be placed -and accessed by all form objects across the project. To enable logging of -validation errors, simply add the following to the `BaseForm` class: +`BaseForm`, расположенный в `lib/form` - это специальный базовый класс формы, от которого наследуются все классы форм. По сути, `BaseForm` является классом, размещение кода в котором делает его (код) доступным любому объекту формы проекта. Для включения логирования ошибок валидации просто добавьте следующие строки в класс `BaseForm`: [php] public static function listenToValidationError($event) @@ -895,30 +725,21 @@ validation errors, simply add the following to the `BaseForm` class: } } -![Logging of validation errors](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_05.png "Web debug toolbar with validation errors") +![Логирование ошибок валидации](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_05.png "Отладочная web-панель с ошибками валидации") -Custom Styling when a Form Element has an Error +Изменение стиля элементов формы с ошибками ----------------------------------------------- -As a final exercise, let's turn to a slightly lighter topic related to the -styling of form elements. Suppose, for example, that the design for the `Product` -page includes special styling for fields that have failed validation. +В качестве заключительного упражнения, давайте обратимся к связанной с оформлением элементов формы теме. Пусть, например, дизайн страницы `Product` содержит специальный стиль для полей, которые не прошедшие проверку. -![Product form with errors](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_02.png "Product form with styled error fields") +![Форма продукта с ошибками](http://www.symfony-project.org/images/more-with-symfony/advanced_forms_02.png "Форма продукта с выделенными ошибками") -Suppose your designer has already implemented the stylesheet that will apply the error -styling to any `input` field inside a `div` with the class `form_error_row`. -How can we easily add the `form_row_error` class to the fields with errors? +Предположим, ваш дизайнер уже реализовал таблицу стилей, которая будет применяться при выделении ошибок любого поля `input` внутри `div` с классом `form_error_row`. Как мы можем упростить добавление класса `form_row_error` к полям, которые содержат ошибки? -The answer lies in a special object called a *form schema formatter*. Every -symfony form uses a *form schema formatter* to determine the exact -html formatting to use when outputting the form elements. By default, symfony -uses a form formatter that employs HTML table tags. +Ответ заключается в специальном объекте, называемом *форматировщик формы*. Каждая форма в symfony использует *форматировщик формы* для определения верного html-форматирования при выводе элементов формы. По умолчанию, symfony +использует форматировщик, который выводит табличные теги HTML. -First, let's create a new form schema formatter class that employs slightly -lighter markup when outputting the form. Create a new file named -`sfWidgetFormSchemaFormatterAc2009.class.php` and place it in the -`lib/widget/` directory (you'll need to create this directory): +Для начала, давайте создадим новый класс форматировщика, который упростит разметку при выводе формы. Создайте новый файл с именем `sfWidgetFormSchemaFormatterAc2009.class.php` и поместите его в каталог `lib/widget/` (вам нужно будет создать этот каталог): [php] class sfWidgetFormSchemaFormatterAc2009 extends sfWidgetFormSchemaFormatter @@ -932,14 +753,10 @@ lighter markup when outputting the form. Create a new file named $decoratorFormat = "
\n %content%
"; } -Though the format of this class is strange, the general idea is that the `renderRow()` -method will use the `$rowFormat` markup to organize its output. A form schema -formatter class offers many other formatting options which I won't cover here -in detail. For more information, consult the +Хотя формат этого класса выглядит странным, общая идея заключается в том, что `renderRow()` при выводе будет использовать указанную `$rowFormat` разметку. Класс форматировщика формы содержит множество других опций форматирования, которые мы не будем здесь подробно рассматривать. Для получения дополнительной информации, смотрите [symfony 1.3 API](http://www.symfony-project.org/api/1_3/sfWidgetFormSchemaFormatter). -To use the new form schema formatter across all form objects in your project, -add the following to `ProjectConfiguration`: +Для использования нового форматировщика во всех объектах форм вашего проекта, добавьте следующий код в `ProjectConfiguration`: [php] class ProjectConfiguration extends sfProjectConfiguration @@ -952,10 +769,7 @@ add the following to `ProjectConfiguration`: } } -The goal is to add a `form_row_error` class to the `form_row` div element -only if a field has failed validation. Add a `%row_class%` token to the -`$rowFormat` property and override the ~`sfWidgetFormSchemaFormatter::formatRow()`~ -method as follows: +Цель состоит в том, чтобы добавить класс `form_row_error` к элементу div только если поле имеет ошибки валидации. Добавьте токен `%row_class%` к `$rowFormat` и измените метод ~`sfWidgetFormSchemaFormatter::formatRow()`~ следующим образом: [php] class sfWidgetFormSchemaFormatterAc2009 extends sfWidgetFormSchemaFormatter @@ -982,20 +796,11 @@ method as follows: } } -With this addition, each element that is output via the `renderRow()` method -will automatically be surrounded by a `form_row_error` `div` if the field has -failed validation. +С этим дополнением каждый элемент с ошибками валидации при выводе через метод `renderRow()` будет автоматически окружаться тэгом `div` с классом `form_row_error`. -Final Thoughts +Заключение -------------- -The form framework is simultaneously one of the most powerful and most -complex components inside symfony. The trade-off for tight form validation, -CSRF protection, and object forms is that extending the framework can quickly -become a daunting task. Gaining a deeper understanding of the form system, -however, is the key toward unlocking its potential. I hope this chapter has -taken you one step closer. +Фреймворк форм - один из самых мощных и одновременно сложных компонентов symfony. Более глубокое понимание фреймворка форм является ключом к раскрытию его потенциала. Я надеюсь, что эта глава помогла вам продвинуться на один шаг вперёд. -Future development of the form framework will focus on preserving the power while -decreasing complexity and giving more flexibility to the developer. The -form framework is only now in its infancy. +В будущем развитие среды разработки форм сосредоточится на том, чтобы при сохранении всей мощи уменьшить сложность и дать большую гибкость разработчику. Среда разработки форм в настоящее время находится всего лишь на начальном этапе развития. diff --git a/more-with-symfony/ru/A-sfWidgetFormGMapAddress.markdown b/more-with-symfony/ru/A-sfWidgetFormGMapAddress.markdown old mode 100755 new mode 100644 index 2e35790..4af871c --- a/more-with-symfony/ru/A-sfWidgetFormGMapAddress.markdown +++ b/more-with-symfony/ru/A-sfWidgetFormGMapAddress.markdown @@ -1,12 +1,11 @@ -Appendix A - JavaScript code for sfWidgetFormGMapAddress +Приложение A - JavaScript-код для sfWidgetFormGMapAddress ======================================================== -The following code is the JavaScript needed to make the -`sfWidgetFormGMapAddress` widget work: +Следующий JavaScript-код необходим для работы виджета `sfWidgetFormGMapAddress`: [js] function sfGmapWidgetWidget(options){ - // this global attributes + // глобальные атрибуты this.lng = null; this.lat = null; this.address = null; @@ -26,39 +25,39 @@ The following code is the JavaScript needed to make the return; } - // retrieve dom element + // получение dom-элементов this.lng = jQuery("#" + this.options.longitude); this.lat = jQuery("#" + this.options.latitude); this.address = jQuery("#" + this.options.address); this.lookup = jQuery("#" + this.options.lookup); - // create the google geocoder object + // создание объекта google geocoder this.geocoder = new GClientGeocoder(); - // create the map + // создание карты this.map = new GMap2(jQuery("#" + this.options.map).get(0)); this.map.setCenter(new GLatLng(this.lat.val(), this.lng.val()), 13); this.map.setUIToDefault(); - // cross reference object + // связывание объектов между собой this.map.sfGmapWidgetWidget = this; this.geocoder.sfGmapWidgetWidget = this; this.lookup.get(0).sfGmapWidgetWidget = this; - // add the default location + // добавление позиции по умолчанию var point = new GLatLng(this.lat.val(), this.lng.val()); var marker = new GMarker(point); this.map.setCenter(point, 15); this.map.addOverlay(marker); - // bind the move action on the map + // связывание действия move карты GEvent.addListener(this.map, "move", function() { var center = this.getCenter(); this.sfGmapWidgetWidget.lng.val(center.lng()); this.sfGmapWidgetWidget.lat.val(center.lat()); }); - // bind the click action on the map + // связывание действия click по карте GEvent.addListener(this.map, "click", function(overlay, latlng) { if (latlng != null) { sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget; @@ -70,7 +69,7 @@ The following code is the JavaScript needed to make the } }); - // bind the click action on the lookup field + // связывание действия click по полю lookup this.lookup.bind('click', function(){ sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget; @@ -86,12 +85,12 @@ The following code is the JavaScript needed to make the sfGmapWidgetWidget.activeWidget = null; sfGmapWidgetWidget.lookupCallback = function(point) { - // get the widget and clear the state variable + // получение виджета и очистка переменной состояния var widget = sfGmapWidgetWidget.activeWidget; sfGmapWidgetWidget.activeWidget = null; if (!point) { - alert("address not found"); + alert("адрес не найден"); return; } @@ -103,27 +102,27 @@ The following code is the JavaScript needed to make the sfGmapWidgetWidget.reverseLookupCallback = function(response) { - // get the widget and clear the state variable + // получение виджета и очиста переменной состояния var widget = sfGmapWidgetWidget.activeWidget; sfGmapWidgetWidget.activeWidget = null; widget.map.clearOverlays(); if (!response || response.Status.code != 200) { - alert('no address found'); + alert('адрес не найден'); return; } - // get information location and init variables + // получение информации о расположении и инициализация переменных var place = response.Placemark[0]; var point = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]); var marker = new GMarker(point); - // add marker and center the map + // добавление маркера и центровка карты widget.map.setCenter(point, 15); widget.map.addOverlay(marker); - // update values + // обновление значений widget.address.val(place.address); widget.lat.val(place.Point.coordinates[1]); widget.lng.val(place.Point.coordinates[0]); From 1bdf6bf4425c1afb17419e21674fa490ab80ff6d Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Thu, 23 Sep 2010 17:41:32 -0700 Subject: [PATCH 02/14] translated & tested --- .../ru/03-Enhance-your-Productivity.markdown | 440 +++++++----------- 1 file changed, 175 insertions(+), 265 deletions(-) mode change 100755 => 100644 more-with-symfony/ru/03-Enhance-your-Productivity.markdown diff --git a/more-with-symfony/ru/03-Enhance-your-Productivity.markdown b/more-with-symfony/ru/03-Enhance-your-Productivity.markdown old mode 100755 new mode 100644 index 91b317e..967c5a4 --- a/more-with-symfony/ru/03-Enhance-your-Productivity.markdown +++ b/more-with-symfony/ru/03-Enhance-your-Productivity.markdown @@ -1,243 +1,201 @@ -Enhance your Productivity -========================= +Повысьте свою продуктивность +============================ -*by Fabien Potencier* +*автор Fabien Potencier; перевод на русский — BRIGADA* -Using symfony itself is a great way to enhance your productivity as a web -developer. Of course, everyone already knows how symfony's detailed exceptions -and web debug toolbar can greatly enhance productivity. This chapter will -teach you some tips and tricks to enhance your productivity even more by -using some new or less well-known symfony features. +Использование symfony само по себе уже повышает вашу продуктивность как вэб-разработчика. +Конечно, все уже знают, как повышают продуктивность исключения symfony и отладочная панель. +Эта глава научит вас некоторым приёмам ещё большего повышения продуктивности при использовании новых и не столь широко известных возможностей symfony. -Start Faster: Customize the Project Creation Process +Ускоряем начало проекта: настройка процесса создания ---------------------------------------------------- -Thanks to the symfony CLI tool, creating a new symfony project is quick and -simple: +Благодаря инструменту командной строки symfony, создание нового проекта происходит быстро и легко: $ php /path/to/symfony generate:project foo --orm=Doctrine -The `generate:project` task generates the default directory structure for your -new project and creates configuration files with sensible defaults. You can -then use other symfony tasks to create applications, install plugins, -configure your model, and more. +Задача `generate:project` создаёт заданную по умолчанию структуру каталогов для вашего нового проекта, а также создаёт соответствующие конфигурационные файлы. +Затем вы можете использовать другие задачи symfony для создания приложений, установки плагинов, конфигурирования модели и так далее. -But the first steps to create a new project are usually always quite the -same: you create a main application, install a bunch of plugins, tweak -some configuration defaults to your liking, and so on. +Но первые шаги по созданию нового проекта почти всегда одни и те же: вы создаёте главное приложение, устанавливаете какой-то набор плагинов, настраиваете под себя некоторые конфигурационные файлы... -As of symfony 1.3, the project creation process can be customized and -automated. +Начиная с symfony 1.3, процесс создания проекта может быть настроен и автоматизирован. >**NOTE** ->As all symfony tasks are classes, it's pretty easy to customize and extend ->them except. The `generate:project` task, however, cannot be easily customized ->because no project exists when the task is executed. +>Поскольку все задачи symfony являются классами, их можно легко настроить и расширить. +>Предыдущая фраза не относится только к одной задаче — `generate:project`, так как на момент выполнения этой задачи проект ещё не существует. -The `generate:project` task takes an `--installer` option, which is a PHP -script that will be executed during the project creation process: +Задача `generate:project` принимает опцию `--installer`, через которую задаётся исполняемый во время процесса создания проекта PHP-скрипт: $ php /path/to/symfony generate:project --installer=/somewhere/my_installer.php -The `/somewhere/my_installer.php` script will be executed in the context of -the `sfGenerateProjectTask` instance, so it has access to the task's methods to -by using the `$this` object. The following sections describe all the available -methods you can use to customize your project creation process. +Скрипт `/somewhere/my_installer.php` выполняется в контексте экземпляра `sfGenerateProjectTask`, таким образом он имеет доступ к методам задачи через объект `$this`. +Следующие разделы описывают все доступные методы, которые вы можете использовать для настройки процесса создания проекта. >**TIP** ->If you enable URL file-access for the `include()` function in your ->`php.ini`, you can even pass a URL as an installer (of course you need ->to be very careful when doing this with a script you know nothing about): +>Если в своём `php.ini` вы включили возможность использования файлов по URL для функции `include()`, вы можете передавать в качестве установщика его URL (конечно же, следует быть очень осторожным, когда вы делаете это со скриптом, о котором ничего не знаете): > > $ symfony generate:project > --installer=http://example.com/sf_installer.php ### `installDir()` -The `installDir()` method mirrors a directory structure (composed of -sub-directories and files) in the newly created project: +Метод `installDir()` задаёт расположение структуры каталогов (которая состоит из подкаталогов и файлов) создаваемого проекта: [php] $this->installDir(dirname(__FILE__).'/skeleton'); ### `runTask()` -The `runTask()` method executes a task. It takes the task name, and a string -representing the arguments and the options you want to pass to it as -arguments: +Метод `runTask()` выполняет задачу. +В качестве параметров он принимает имя задачи и строку, представляющую собой передаваемые в задачу аргументы: [php] $this->runTask('configure:author', "'Fabien Potencier'"); -Arguments and options can also be passed as arrays: +Аргументы также можно передать в массиве: [php] $this->runTask('configure:author', array('author' => 'Fabien Potencier')); >**TIP** ->The task shortcut names also work as expected: +>Сокращённая запись имени задачи также работает: > > [php] > $this->runTask('cc'); -This method can of course be used to install plugins: +Этот метод можно использовать при установке плагинов: [php] $this->runTask('plugin:install', 'sfDoctrineGuardPlugin'); -To install a specific version of a plugin, just pass the needed options: +Для установки определённой версии плагина необходимо передать соответствующие опции: [php] - $this->runTask('plugin:install', 'sfDoctrineGuardPlugin', array('release' => '10.0.0', 'stability' => beta')); + $this->runTask('plugin:install', 'sfDoctrineGuardPlugin', array('release' => '10.0.0', 'stability' => 'beta')); >**TIP** ->To execute a task from a freshly installed plugin, the tasks need to be ->reloaded first: +>Для запуска задач из только что установленного плагина, необходимо перезагрузить список задач: > > [php] > $this->reloadTasks(); -If you create a new application and want to use tasks that relys on a -specific application like `generate:module`, you must change the configuration -context yourself: +Если вы создаёте новое приложение и хотите использовать задачи, которые зависят от определённой конфигурации (например, `generate:module`), вы должны самостоятельно её установить: [php] $this->setConfiguration($this->createConfiguration('frontend', 'dev')); -### Loggers +### Логгеры -To give feedback to the developer when the installer script runs, you can log -things pretty easily: +Чтобы реализовать взаимодействие с разработчиком, запустившим установочный скрипт, вы можете легко выводить в лог сообщения: [php] - // a simple log - $this->log('some installation message'); + // простой лог + $this->log('какая-нибудь чушь про установку'); - // log a block - $this->logBlock('Fabien\'s Crazy Installer', 'ERROR_LARGE'); + // лог блока + $this->logBlock('Чумовой Установщик Василия Пупкина', 'ERROR_LARGE'); - // log in a section - $this->logSection('install', 'install some crazy files'); + // лог в разделе + $this->logSection('install', 'устанавливаем дикое количество файлов'); -### User Interaction +### Взаимодействие с пользователем -The `askConfirmation()`, `askAndValidate()`, and `ask()` methods allow you to -ask questions and make your installation process dynamically configurable. +Методы `askConfirmation()`, `askAndValidate()` и `ask()` позволяют вам задавать вопросы и, таким образом, реализовать динамически конфигурируемый процесс установки. -If you just need a confirmation, use the `askConfirmation()` method: +Если вам нужно простое подтверждение, используйте метод `askConfirmation()`: [php] - if (!$this->askConfirmation('Are you sure you want to run this crazy installer?')) + if (!$this->askConfirmation('Вы действительно хотите запустить этот дикий установщик?')) { - $this->logSection('install', 'You made the right choice!'); + $this->logSection('install', 'Вы сделали правильный выбор! :)'); return; } -You can also ask any question and get the user's answer as a string by using -the `ask()` method: +Также вы можете задавать пользователю любые вопросы и получать от него строковые ответы с помощью метода `ask()`: [php] - $secret = $this->ask('Give a unique string for the CSRF secret:'); + $secret = $this->ask('Укажите уникальную строку для использования в CSRF-защите:'); -And if you want to validate the answer, use the `askAndValidate()` method: +А ещё вы можете проверять ответы при помощи метода `askAndValidate()`: [php] - $validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!')); - $email = $this->askAndValidate('Please, give me your email:', $validator); + $validator = new sfValidatorEmail(array(), array('invalid' => 'хм... кажется вы ввели не адрес email!')); + $email = $this->askAndValidate('Пожалуйста, укажите свой адрес электронной почты:', $validator); -### Filesystem Operations +### Операции файловой системы -If you want to do filesystem changes, you can access the symfony filesystem -object: +Если вы хотите изменить файловую систему, вы можете получить доступ к symfony-объекту файловой системы: [php] $this->getFilesystem()->...(); >**SIDEBAR** ->The Sandbox Creation Process +>Процесс создания песочницы (sandbox) > ->The symfony sandbox is a pre-packaged symfony project with a ready-made ->application and a pre-configured SQLite database. Anybody can create a sandbox ->by using its installer script: +>Песочница symfony — это подготовленный проект с готовой базой данных SQLite. +>Создать песочницу можно используя её установочный скрипт: > > $ php symfony generate:project --installer=/path/to/symfony/data/bin/sandbox_installer.php > ->Have a look at the `symfony/data/bin/sandbox_installer.php` script to have a ->working example of an installer script. +>Скрипт `symfony/data/bin/sandbox_installer.php` является готовым примером установщика. -The installer script is just another PHP file. So, you can do pretty anything -you want. Instead of running the same tasks again and again each time you -create a new symfony project, you can create your own installer script and -tweak your symfony project installations the way you want. Creating a new -project with an installer is much faster and prevents you from missing -steps. You can even share your installer script with others! +Скрипт установщика — это всего лишь PHP-файл. +Посему, вы можете делать что пожелаете. +Вместо того, чтобы при создании новых проектов снова и снова запускать определённые задачи, вы можете создать свой собственный скрипт установщика и настраивать процесс установки под себя. +Создание нового проекта через скрипт ускоряет выполнение задачи и исключает использование вами одних и тех же действий. +А ещё вы можете поделиться вашим скриптом с другими! >**TIP** ->In [Chapter 06](#chapter_06), we will use a custom installer. The code for it ->can be found in [Appendix B](#chapter_b). +>В [Главе 6](#chapter_06) мы используем свой собственный установщик. +>Его код можно посмотреть в [Приложении B](#chapter_b). -Develop Faster --------------- +Разрабатывайте быстрее +---------------------- -From PHP code to CLI tasks, programming means a lot of typing. Let's see how -to reduce this to the bare minimum. +И при написании PHP-кода, и при написании задач командной строки, программирование подразумевает ввод больших объёмов кода. +Давайте посмотрим, как можно сократить затрачиваемое на это время до минимума. -### Choosing your IDE +### Подберите себе IDE -Using an IDE helps the developer to be more productive in more than one way. +Использование интегрированной среды разработки (IDE) позволяет повысить продуктивности не одним способом. -First, most modern IDEs provide PHP autocompletion out of the box. This means -that you only need to type the first few character of a method name. This -also means that even if you don't remember the method name, you are not forced -to have look at the API as the IDE will suggest all the available methods of -the current object. +Во-первых, большинство современных IDE поддерживают автозавершение для вводимого PHP-кода. +Это означает, что вам необходимо набирать только первые несколько символов имени метода. +Это также означает, что даже если вы не помните имя метода, вам всё ещё не нужно переключаться на чтение API, так как IDE может вывести список всех методов текущего объекта. -Additionally, some IDEs, like PHPEdit or Netbeans, know even more about symfony -and provide specific integration with symfony projects. +Во-вторых, некоторые IDE, например PHPEdit и Netbeans, знают о symfony несколько больше, и определённым образом интегрируются с symfony-проектами. >**SIDEBAR** ->Text Editors +>Текстовые редакторы > ->Some users prefer to use a text editor for their programming work, mainly ->because text editors are faster than any IDE. Of course, text editors provide ->less IDE-oriented features. Most popular editors, however, offer ->plugins/extensions that can be used to enhance your user experience and ->make the editor work more efficiently with PHP and symfony projects. +>Некоторые пользователи предпочитают в своей программистской работе использовать обыкновенные текстовые редакторы, так как работают они быстрее любой IDE. +>Безусловно, текстовый редактор обеспечивает меньше возможностей, чем полноценная IDE. +>Однако большинство популярных редакторов имеют возможность подключения плагинов/дополнений, расширяющих пользовательские возможности при разработке PHP и symfony-проектов. > ->For example, a lot of Linux users tends to use VIM for all their work. ->For these developers, the [vim-symfony](http://github.com/geoffrey/vim-symfony) ->extension is available. VIM-symfony is a set of VIM scripts that integrates ->symfony into your favorite editor. Using vim-symfony, you can easily create vim ->macros and commands to streamline your symfony development. It also ->bundles a set of default commands that put a number of configuration ->files at your fingertips (schema, routing, etc) and enable you to ->easily switch from actions to templates. +>Например, многие Linux-пользователи предпочитают использовать VIM. +>Этим пользователям доступно расширение [vim-symfony](http://github.com/geoffrey/vim-symfony). +>VIM-symfony — это набор VIM-скриптов, которые интегрируют symfony с их любимым редактором. +>Используя vim-symfony, можно легко создавать макросы и команды vim для упрощения разработки под symfony. +>Также, этот набор скриптов содержит набор команд, располагающих некоторые конфигурационные файлы "под кончиками пальцев" и позволяет легко переключаться от действий к шаблонам. > ->Some MacOS X users use TextMate. These developers can install the symfony ->[bundle](http://github.com/denderello/symfony-tmbundle), which adds a lot ->of time-saving macros and shortcuts for day-to-day activities. +>Некоторые пользователи MacOS X используют TextMate. +>Эти разработчики могут установить [пакет](http://github.com/denderello/symfony-tmbundle), который добавит несколько макросов и ярлыков для повседневных задач. -#### Using an IDE that supports symfony +#### Использование IDE, которая поддерживает symfony -Some IDEs, like [PHPEdit 3.4](http://www.phpedit.com/en/presentation/extensions/symfony) -and [NetBeans 6.8](http://www.netbeans.org/community/releases/68/), have -native support for symfony, and so provide a finely-grained integration -with the framework. Have a look at their documentation to learn more about -their symfony specific support, and how it can help you develop faster. +Некоторые IDE, например [PHPEdit 3.4](http://www.phpedit.com/en/presentation/extensions/symfony) и [NetBeans 6.8](http://www.netbeans.org/community/releases/68/), имеют встроенную поддержку symfony, и обеспечивают полноценную интеграцию с этим фреймворком. +Посмотрите их документацию для того чтобы узнать больше об этой поддержке и о том, как это может помочь вам вести свои разработки быстрее. -#### Helping the IDE +#### Помощь IDE -PHP autocompletion in IDEs only works for methods that are explicitly defined -in the PHP code. But if your code uses the `__call()` or `__get()` "magic" -methods, IDEs have no way to guess the available methods or properties. The -good news is that you can help most IDEs by providing the methods and/or -properties in a PHPDoc block (by using the `@method` and `@property` -annotations respectively). +Автозавершение в IDE работает только для методов, которые явно определены в коде. +Однако, если вы используете "волшебные" методы типа `__call()` или `__get()`, у IDE нет возможности показать вам доступные свойства и методы. +Хорошая новость — в большинстве случаев, вы можете сами помочь IDE, указав методы и/или свойства в блоке PHPDoc (используя соответствующие комментарии `@method` и `@property`). -Let's say you have a `Message` class with a dynamic property (`message`) and a -dynamic method (`getMessage()`). The following code shows you how an IDE can -know about them without any explicit definition in the PHP code: +Предположим, у вас есть класс `Message` с динамическим свойством (`message`) и динамическим методом (`getMessage()`). +Следующий код демонстрирует, как IDE сможет узнать о них без какого-либо явного определения в коде: [php] /** @@ -258,13 +216,11 @@ know about them without any explicit definition in the PHP code: } } -Even if the `getMessage()` method does not exist, it will be recognized by the -IDE thanks to the `@method` annotation. The same goes for the `message` -property as we have added a `@property` annotation. +Даже если метод `getMessage()` не существует, он распознаётся IDE благодаря аннотации `@method`. +То же самое происходит и в случае со свойством `message`, для которого добавлена аннотация `@property`. -This technique is used by the `doctrine:build-model` task. For instance, a -Doctrine `MailMessage` class with two columns (`message` and `priority`) looks -like the following: +Эта техника используется задачей `doctrine:build-model`. +Например, Doctrine-класс `MailMessage` с двумя столбцами (`message` и `priority`) выглядит следующим образом: [php] /** @@ -307,64 +263,52 @@ like the following: } } -Find Documentation Faster -------------------------- +Быстрый поиск документации +-------------------------- -As symfony is a large framework with many features, it is not always easy to -remember all the configuration possibilities, or all the classes and methods -at your disposal. As we have seen before, using an IDE can go a long way in -providing you autocompletion. Let's explore how existing tools can be leveraged to -find answers as fast as possible. +Поскольку symfony — большой фреймворк с богатыми возможностями, не так-то легко запомнить все тонкости конфигурирования или все классы и методы. +Как мы уже видели, использование IDE может очень сильно помочь даже одним автозавершением. +Давайте исследуем, как можно использовать существующие инструменты для быстрого поиска ответов. -### Online API +### Онлайн-документация по API -The fastest way to find documentation about a class or a method is to browse -the online [API](http://www.symfony-project.org/api/1_3/). +Наибыстрейшим способом получения документации о классе или методе является просмотр online-версии [API](http://www.symfony-project.org/api/1_3/). -Even more interesting is the built-in API search engine. The search allows -you to rapidly find a class or a method with only a few keystrokes. After -entering a few letters into the search box of the API page, a quick search -results box will appear in real-time with useful suggestions. +Ещё более интересным представляется использование встроенного в API поискового механизма. +Он позволяет вам быстро находить класс или метод в несколько нажатий клавиш. +После ввода нескольких букв в поисковую строку, открывается список соответствующих вариантов. -You can search by typing the beginning of a class name: +Вы можете выполнять поиск, введя только начало имени класса: -![API Search](http://www.symfony-project.org/images/more-with-symfony/api_search_1.png "API Search") +![Поиск в API](http://www.symfony-project.org/images/more-with-symfony/api_search_1.png "Поиск в API") -or of a method name: +или имени метода: -![API Search](http://www.symfony-project.org/images/more-with-symfony/api_search_2.png "API Search") +![Поиск в API](http://www.symfony-project.org/images/more-with-symfony/api_search_2.png "Поиск в API") -or a class name followed by `::` to list all available methods: +или ввести за именем класса `::` для получения списка доступных методов: -![API Search](http://www.symfony-project.org/images/more-with-symfony/api_search_3.png "API Search") +![Поиск в API](http://www.symfony-project.org/images/more-with-symfony/api_search_3.png "Поиск в API") -or enter the beginning of a method name to further refine the possibilities: +или продолжить вводить имя метода: -![API Search](http://www.symfony-project.org/images/more-with-symfony/api_search_4.png "API Search") +![Поиск в API](http://www.symfony-project.org/images/more-with-symfony/api_search_4.png "Поиск в API") -If you want to list all classes of a package, just type the package name and -submit the request. +Если вы хотите получить список всех классов пакета, просто введите имя этого пакета и отправьте запрос. -You can even integrate the symfony API search in your browser. This way, you -don't even need to navigate to the symfony website to look for something. This is -possible because we provide native [OpenSearch](http://www.opensearch.org/) -support for the symfony API. +Вы можете интегрировать поиск по API в свой браузер. +В этом случае вам не придётся вначале переходить на сайт symfony, а затем выполнять поиск. +Эта возможность доступна, так как мы обеспечиваем поддержку механизма [OpenSearch](http://www.opensearch.org/). -If you use Firefox, the symfony API search engines will show up automatically -in the search engine menu. You can also click on the "API OpenSearch" link -from the API documentation section to add one of them to your browser search -box. +Если вы используете Firefox, механизм поиска API можно автоматически встроить в поисковом меню. +Для этого вам потребуется нажать ссылку "API OpenSearch" в соответствующем разделе документации и произойдёт добавление в поисковое меню вашего браузера. >**NOTE** ->You can have a look at a screencast that shows how the symfony API search ->engine integrates well with Firefox on the symfony ->[blog](http://www.symfony-project.org/blog/2009/02/24/opensearch-support-for-the-symfony-api). +>Вы можете [посмотреть](http://www.symfony-project.org/blog/2009/02/24/opensearch-support-for-the-symfony-api) видеоруководство, в котором демонстрируется интеграция поискового механизма в Firefox. -### Cheat Sheets +### Диаграммы-шпаргалки -If you want to quickly access information about the main parts of the framework, -a large collection of [cheat sheets](http://trac.symfony-project.org/wiki/CheatSheets) -is available: +Если вы хотите иметь быстрый доступ к информации о главных частях фреймворка, у нас есть коллекция [диаграмм-шпаргалок](http://trac.symfony-project.org/wiki/CheatSheets): * [Directory Structure and CLI](http://andreiabohner.files.wordpress.com/2007/03/cheatsheetsymfony001_enus.pdf) * [View](http://andreiabohner.files.wordpress.com/2007/08/sfviewfirstpartrefcard.pdf) @@ -376,104 +320,74 @@ is available: * [Doctrine](http://www.phpdoctrine.org/Doctrine-Cheat-Sheet.pdf) >**NOTE** ->Some of these cheat sheets have not yet been updated for symfony 1.3. +>Некоторые из этих шпаргалок не обновлялись после появления symfony 1.3. -### Offline Documentation +### Бумажная документация -Questions about configuration are best answered by the symfony reference -guide. This is a book you should keep with you whenever you develop with -symfony. The book is the fastest way to find every available configuration -thanks to a very detailed table of contents, an index of terms, -cross-references inside the chapters, tables, and much more. +Вопросы конфигуририрования наиболее полно освещены в *Справочнике по Symfony* (Symfony Reference Guide). +Это именно та книга, к которой вам стоит обращаться при возникновении вопросов, появляющихся по мере разработки для symfony. +Она является наибыстрейшим способом получения всех возможных параметров конфигурации, благодаря подробному оглавлению, списку терминов и определений, перекрёстным ссылкам в главах, таблицам и многому другому. -You can browse this book -[online](http://www.symfony-project.org/reference/1_3/en/), buy a -[printed](http://books.sensiolabs.com/book/the-symfony-1-3-reference-guide) -copy of it, or even download a -[PDF](http://www.symfony-project.org/get/pdf/reference-1.3-en.pdf) version. +Вы можете посмотреть эту книгу [онлайн](http://www.symfony-project.org/reference/1_3/en/), купить [отпечатанную](http://books.sensiolabs.com/book/the-symfony-1-3-reference-guide) копию, или скачать в виде [PDF](http://www.symfony-project.org/get/pdf/reference-1.3-en.pdf). -### Online Tools +### Онлайн-инструменты -As seen at the beginning of this chapter, symfony provides a nice toolset to -help you get started faster. Eventually, you will finish your -project, and it will be time to deploy it to production. +Как замечено в начале этой главы, symfony предоставляет хороший комплект инструментов, чтобы помочь вам вести свои разработки быстрее. +Рано или поздно, вы закончите свой проект и настанет пора развернуть его. -To check that your project is ready for deployment, you can use the online -deployment [checklist](http://symfony-check.org/). This website covers the -major points you need to check before going to production. +Чтобы проверить готовность вашего проекта к развёртыванию, вы можете использовать [онлайн-тестировщик](http://symfony-check.org/). Этот веб-сайт контролирует все важные пункты, которые вам необходимо проверить перед развёртыванием. -Debug Faster ------------- +Отлаживайте быстрее +------------------- -When an error occurs in the development environment, symfony displays a nice -exception page filled with useful information. You can, for instance, have a look -at the stack trace and the files that have been executed. If you setup the -~`sf_file_link_format`~ setting in the `settings.yml` configuration file (see -below), you can even click on the filenames and the related file will be -opened at the right line in your favorite text editor or IDE. This is a -great example of a very small feature that can save you tons of time when -debugging a problem. +Когда в отладочном окружении происходит ошибка, symfony отображает заполненную полезной информацией страницу исключения. +Вы можете, например, посмотреть содержимое стека функций и увидеть задействованные файлы. +Если вы установите настройку ~`sf_file_link_format`~ в конфигурационном файле `settings.yml` (см. ниже), вы даже сможете кликать на имена файлов, и соответствующие файлы будут открываться на нужной строке в вашем текстовом редакторе или IDE. +Это великолепный пример того, как маленькая функция сохраняет массу вашего времени при отладке. >**NOTE** ->The log and view panels in the web debug toolbar also display filenames ->(especially when XDebug is enabled) that become clickable when you set the ->`sf_file_link_format` setting. +>Панели log и view вэб-отладчика также отображают имена файлов (когда включён XDebug), а при установке `sf_file_link_format` они становятся ещё и кликабельными. -By default, the `sf_file_link_format` is empty and symfony defaults to the -value of the -[`xdebug.file_link_format`](http://xdebug.org/docs/all_settings#file_link_format) -PHP configuration value if it exists (setting `xdebug.file_link_format` in -`php.ini` allows recent versions of XDebug to add links for all filenames in -the stack trace). +По умолчанию, `sf_file_link_format` имеет пустое значение, и symfony использует значение [`xdebug.file_link_format`](http://xdebug.org/docs/all_settings#file_link_format) конфигурации PHP если оно существует (установка `xdebug.file_link_format` в `php.ini` для большинства версий XDebug добавляет ссылки для имён файлов в трассировке). -The value for `sf_file_link_format` depends on your IDE and Operating System. -For instance, if you want to open files in ~TextMate~, add the following to -`settings.yml`: +Значение `sf_file_link_format` зависит от вашей IDE и операционной системы. +Например, если вы хотите открывать файлы в ~TextMate~, добавьте следующие строки в файле `settings.yml`: [yml] dev: .settings: file_link_format: txmt://open?url=file://%f&line=%l -The `%f` placeholder is replaced by symfony with the absolute path of the file -and the `%l` placeholder is replaced with the line number. +Шаблон `%f` заменяется symfony на абсолютный путь к файлу, а `%l` — на номер строки. -If you use VIM, the configuration is more involved and is described online for -[symfony](http://geekblog.over-blog.com/article-symfony-open-exceptions-files-in-remote-vim-sessions-37895120.html) -and [XDebug](http://www.koch.ro/blog/index.php?/archives/77-Firefox,-VIM,-Xdebug-Jumping-to-the-error-line.html). +Если вы используете VIM, конфигурация более запутанная и описана онлайн для [symfony](http://geekblog.over-blog.com/article-symfony-open-exceptions-files-in-remote-vim-sessions-37895120.html) и [XDebug](http://www.koch.ro/blog/index.php?/archives/77-Firefox,-VIM,-Xdebug-Jumping-to-the-error-line.html). >**NOTE** ->Use your favorite search engine to learn how to configure your IDE. You can ->look for configuration of the `sf_file_link_format` or `xdebug.file_link_format` ->as both work in the same way. - -Test Faster ------------ - -### Record Your Functional Tests - -Functional tests simulate user interaction to thoroughly test the -integration of all the pieces of your application. Writing functional tests is -easy but time consuming. But as each functional test file is a scenario that -simulates a user browsing your website, and because browsing an application is -faster than writing PHP code, what if you could record a browser session and -have it automatically converted to PHP code? Thankfully, symfony has such a -plugin. It's called -[swFunctionalTestGenerationPlugin](http://www.symfony-project.org/plugins/swFunctionalTestGenerationPlugin), -and it allows you to generate ready-to-be-customized test skeletons in a matter -of minutes. Of course, you will still need to add the proper tester calls to -make it useful, but this is nonetheless a great time-saver. - -The plugin works by registering a symfony filter that will intercept all -requests, and convert them to functional test code. After installing the -plugin the usual way, you need to enable it. Open the `filters.yml` of your -application and add the following lines after the comment line: +>Используйте свой любимый поисковик, чтобы узнать, как конфигурируется конкретно ваша IDE. +>Вы можете искать конфигурацию `sf_file_link_format` или `xdebug.file_link_format` — они работают одинаково. + +Тестируйте быстрее +------------------ + +### Пишите свои функциональные тесты + +Функциональные тесты моделируют взаимодействие с пользователем, что позволяет протестировать интеграцию всех компонентов вашего приложения. +Написание функциональных тестов является лёгкой, хотя и трудоёмкой задачей. +Поскольку каждый файл функционального теста является сценарием, моделирующим поведение пользователя, который просматривает ваш сайт, и так как просмотр приложения происходит, очевидно, быстрее написания PHP-кода, то что, если вы запишете сессию просмотра сайта и будете иметь возможность конвертировать эту запись в PHP? +К счастью, symfony имеет такой плагин. +Он называется [swFunctionalTestGenerationPlugin](http://www.symfony-project.org/plugins/swFunctionalTestGenerationPlugin), +и позволяет вам сгенерировать скелет теста буквально за минуты. +Конечно, вам всё ещё надо будет добавить правильных вызовов тестера, но это всё равно экономит вам время. + +Плагин регистрирует symfony-фильтр, который перехватывает все запросы и конвертирует их в код функционального теста. +После установки плагина, вам следует включить его. +Откройте `filters.yml` вашего приложения и добавьте следующие строки после строки комментария: [php] functional_test: class: swFilterFunctionalTest -Next, enable the plugin in your `ProjectConfiguration` class: +Далее, включите плагин в своём классе `ProjectConfiguration`: [php] // config/ProjectConfiguration.class.php @@ -487,25 +401,21 @@ Next, enable the plugin in your `ProjectConfiguration` class: } } -As the plugin uses the web debug toolbar as its main user interface, be sure -to have it enabled (which the case in the development environment by default). -When enabled, a new menu named "Functional Test" is made available. In this -panel, you can start recording a session by clicking on the "Activate" link, -and reset the current session by clicking on "Reset". When you are done, copy -and paste the code from the textarea to a test file and start customizing it. +Так как плагин использует отладочную вэб-панель в качестве своего главного пользовательского интерфейса, убедитесь что она включена (обычно, панель включена по умолчанию в окружении dev)). +Когда всё включено, становится доступно новое меню "Functional Test". +В этой панели вы можете начать запись сессии кликом на ссылку "Activate", и сбросить текущую сессию кликом на "Reset". +Когда вы закончите, скопируйте код из текстового поля в файл теста и приступайте к его модификации. -### Run your Test Suite faster +### Запускайте свои тесты быстрее -When you have a large suite of tests, it can be very time consuming to launch -all tests every time you make a change, especially if some tests fail. Each -time you fix a test, you should run the whole test suite again to ensure -that you have not broken other tests. But until the failed tests are fixed, -there is no point in re-executing all the other tests. To speed up this process, -the `test:all` task has an `--only-failed` (`-f` as a shortcut) option that forces -the task to only re-execute tests that failed during the previous run: +Когда у вас есть большой набор тестов, достаточно трудоёмко запускать все тесты при проверке сделанных изменений, особенно если некоторые тесты перестали работать. +Каждый раз, когда вы изменяете тест, вам необходимо снова и снова запускать весь комплекс тестов, чтобы гарантировать неповреждённость других тестов. +Но до тех пор, пока вы не почините ошибочный тест, запускать другие тесты не требуется. +Для ускорения этого процесса задача `test:all` имеет опцию `--only-failed` (или `-f` как сокращённая запись), которая позволяет ускорить задачу путём выполнения только тех тестов, которые провалились при предыдущем запуске: $ php symfony test:all --only-failed -On first execution, all tests are run as usual. But for subsequent test runs, only tests that failed last time are executed. As you fix your code, some tests -will pass, and will be removed from subsequent runs. When all tests pass -again, the full test suite is run... you can then rinse and repeat. \ No newline at end of file +При первом запуске, все тесты выполняются как обычно. +Но в дальнейшем будут выполняться только те тесты, которые провалились. +По мере исправления кода, тесты начнут выполняться и удаляться при дальнейших запусках. +Когда все тесты пройдут, будет запущен полный комплекс тестов... и возможно вам придётся повторить выправление ошибок. \ No newline at end of file From c2ef6c01dbaa1570c313e04cde9dc50f3c29a744 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Fri, 24 Sep 2010 07:16:21 -0700 Subject: [PATCH 03/14] 100% translated --- more-with-symfony/ru/B-Custom-Installer-Example.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 more-with-symfony/ru/B-Custom-Installer-Example.markdown diff --git a/more-with-symfony/ru/B-Custom-Installer-Example.markdown b/more-with-symfony/ru/B-Custom-Installer-Example.markdown old mode 100755 new mode 100644 index 567fd08..fbdce51 --- a/more-with-symfony/ru/B-Custom-Installer-Example.markdown +++ b/more-with-symfony/ru/B-Custom-Installer-Example.markdown @@ -1,7 +1,7 @@ -Appendix B - Custom Installer Example -===================================== +Приложение B - Пример заказного установщика +=========================================== -The following PHP code is a custom installer used in [Chapter 06](#chapter_06): +Ниже приведён пример PHP-кода, реализующего заказной установщик, используемы в [Главе 06](#chapter_06): [php] Date: Fri, 24 Sep 2010 07:17:22 -0700 Subject: [PATCH 04/14] --- more-with-symfony/ru/B-Custom-Installer-Example.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/B-Custom-Installer-Example.markdown b/more-with-symfony/ru/B-Custom-Installer-Example.markdown index fbdce51..e3f1cd2 100644 --- a/more-with-symfony/ru/B-Custom-Installer-Example.markdown +++ b/more-with-symfony/ru/B-Custom-Installer-Example.markdown @@ -1,7 +1,7 @@ Приложение B - Пример заказного установщика =========================================== -Ниже приведён пример PHP-кода, реализующего заказной установщик, используемы в [Главе 06](#chapter_06): +Ниже приведён пример PHP-кода, реализующего заказной установщик, который используется в [Главе 06](#chapter_06): [php] Date: Sat, 25 Sep 2010 02:28:24 -0700 Subject: [PATCH 05/14] --- more-with-symfony/ru/04-Emails.markdown | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) mode change 100755 => 100644 more-with-symfony/ru/04-Emails.markdown diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown old mode 100755 new mode 100644 index b2b968f..159a123 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -10,10 +10,10 @@ make sending emails even more flexible and powerful. This chapter will teach you how to put all the power at your disposal. >**NOTE** ->symfony 1.3 embeds Swift Mailer version 4.1. +>symfony 1.3 включает Swift Mailer версии 4.1. -Introduction ------------- +Введение +-------- Email management in symfony is centered around a mailer object. And like many other core symfony objects, the mailer is a factory. It is configured in the @@ -31,19 +31,17 @@ This tutorial explains the Swift Mailer integration in symfony. If you want to learn the nitty-gritty details of the Swift Mailer library itself, refer to its dedicated [documentation](http://www.swiftmailer.org/docs). -Sending Emails from an Action ------------------------------ +Отправка электронной почты из действия +-------------------------------------- -From an action, retrieving the mailer instance is made simple with the -`getMailer()` shortcut method: +From an action, retrieving the mailer instance is made simple with the `getMailer()` shortcut method: [php] $mailer = $this->getMailer(); -### The Fastest Way +### Наибыстрейший путь -Sending an email is then as simple as using the ~`sfMailer::composeAndSend()`~ -method: +Отправка электронной столь же проста, как и использование метода ~`sfMailer::composeAndSend()`~: [php] $this->getMailer()->composeAndSend( @@ -53,15 +51,14 @@ method: 'Body' ); -The `composeAndSend()` method takes four arguments: +Метод `composeAndSend()` принимает четыре аргумента: - * the sender email address (`from`); - * the recipient email address(es) (`to`); - * the subject of the message; - * the body of the message. + * адрес электронной почты отправителя (`from`); + * один или несколько адресов электронной почты получателя (`to`); + * тема письма; + * тело письма. -Whenever a method takes an email address as a parameter, you can pass a string -or an array: +Whenever a method takes an email address as a parameter, you can pass a string or an array: [php] $address = 'fabien@example.com'; From 048dfee3bd7964c4304a089115327ccf8415a7da Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sat, 25 Sep 2010 02:55:29 -0700 Subject: [PATCH 06/14] --- more-with-symfony/ru/04-Emails.markdown | 263 ++++++++---------------- 1 file changed, 87 insertions(+), 176 deletions(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index 159a123..c75fd3f 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -80,12 +80,10 @@ of emails as the second argument of the method: ); $this->getMailer()->composeAndSend('from@example.com', $to, 'Subject', 'Body'); -### The Flexible Way +### Гибкий способ -If you need more flexibility, you can also use the ~`sfMailer::compose()`~ -method to create a message, customize it the way you want, and eventually send -it. This is useful, for instance, when you need to add an -~attachment|email attachment~ as shown below: +Если вам требуется большая гибкость, для создания сообщения вы можете использовать метод ~`sfMailer::compose()`~, customize it the way you want, and eventually send it. +This is useful, for instance, when you need to add an ~attachment|email attachment~ as shown below: [php] // create a message object @@ -113,15 +111,11 @@ You can also create a message object directly for even more flexibility: $this->getMailer()->send($message); >**TIP** ->The ["Creating Messages"](http://swiftmailer.org/docs/messages) and ->["Message Headers"](http://swiftmailer.org/docs/headers) sections of the ->Swift Mailer official documentation describe all you need to know about ->creating messages. +>The ["Creating Messages"](http://swiftmailer.org/docs/messages) and ["Message Headers"](http://swiftmailer.org/docs/headers) sections of the Swift Mailer official documentation describe all you need to know about creating messages. ### Using the Symfony View -Sending your emails from your actions allows you to leverage the power of -partials and components quite easily. +Sending your emails from your actions allows you to leverage the power of partials and components quite easily. [php] $message->setBody($this->getPartial('partial_name', $arguments)); @@ -129,9 +123,7 @@ partials and components quite easily. Configuration ------------- -As any other symfony factory, the mailer can be configured in the -`factories.yml` configuration file. The default configuration reads as -follows: +As any other symfony factory, the mailer can be configured in the `factories.yml` configuration file. The default configuration reads as follows: [yml] mailer: @@ -149,9 +141,7 @@ follows: username: ~ password: ~ -When creating a new application, the local `factories.yml` configuration file -overrides the default configuration with some sensible defaults for the -`prod`, `env`, and `test` environments: +When creating a new application, the local `factories.yml` configuration file overrides the default configuration with some sensible defaults for the `prod`, `env`, and `test` environments: [yml] test: @@ -167,12 +157,9 @@ overrides the default configuration with some sensible defaults for the The Delivery Strategy --------------------- -One of the most useful feature of the Swift Mailer integration in symfony is -the delivery strategy. The delivery strategy allows you to tell symfony how to -deliver email messages and is configured via the ~`delivery_strategy`~ setting -of `factories.yml`. The strategy changes the way the -~`send()`|`sfMailer::send()`~ method behaves. Four strategies are available by -default, which should suit all the common needs: +One of the most useful feature of the Swift Mailer integration in symfony is the delivery strategy. +The delivery strategy allows you to tell symfony how to deliver email messages and is configured via the ~`delivery_strategy`~ setting of `factories.yml`. +The strategy changes the way the ~`send()`|`sfMailer::send()`~ method behaves. Four strategies are available by default, which should suit all the common needs: * `realtime`: Messages are sent in realtime. * `single_address`: Messages are sent to a single address. @@ -181,39 +168,28 @@ default, which should suit all the common needs: ### The ~`realtime`~ Strategy -The `realtime` strategy is the default delivery strategy, and the easiest to -setup as there is nothing special to do. +The `realtime` strategy is the default delivery strategy, and the easiest to setup as there is nothing special to do. -Email messages are sent via the transport configured in the `transport` -section of the `factories.yml` configuration file (see the next section for -more information about how to configure the mail transport). +Email messages are sent via the transport configured in the `transport` section of the `factories.yml` configuration file (see the next section for more information about how to configure the mail transport). ### The ~`single_address`~ Strategy -With the `single_address` strategy, all messages are sent to a single address, -configured via the `delivery_address` setting. +With the `single_address` strategy, all messages are sent to a single address, configured via the `delivery_address` setting. -This strategy is really useful in the development environment to avoid sending -messages to real users, but still allow the developer to check the rendered -message in an email reader. +This strategy is really useful in the development environment to avoid sending messages to real users, but still allow the developer to check the rendered message in an email reader. >**TIP** ->If you need to verify the original `to`, `cc`, and `bcc` recipients, they are ->available as values of the following headers: `X-Swift-To`, `X-Swift-Cc`, and ->`X-Swift-Bcc` respectively. +>If you need to verify the original `to`, `cc`, and `bcc` recipients, they are available as values of the following headers: `X-Swift-To`, `X-Swift-Cc`, and `X-Swift-Bcc` respectively. -Email messages are sent via the same email transport as the one used for the -`realtime` strategy. +Email messages are sent via the same email transport as the one used for the `realtime` strategy. ### The ~`spool`~ Strategy With the `spool` strategy, messages are stored in a queue. -This is the best strategy for the production environment, as web requests do -not wait for the emails to be sent. +This is the best strategy for the production environment, as web requests do not wait for the emails to be sent. -The `spool` class is configured with the ~`spool_class`~ setting. By default, -symfony comes bundled with three of them: +The `spool` class is configured with the ~`spool_class`~ setting. By default, symfony comes bundled with three of them: * ~`Swift_FileSpool`~: Messages are stored on the filesystem. @@ -221,24 +197,19 @@ symfony comes bundled with three of them: * ~`Swift_PropelSpool`~: Messages are stored in a Propel model. -When the spool is instantiated, the ~`spool_arguments`~ setting is used as the -constructor arguments. Here are the options available for the built-in queues -classes: +When the spool is instantiated, the ~`spool_arguments`~ setting is used as the constructor arguments. Here are the options available for the built-in queues classes: * `Swift_FileSpool`: - * The absolute path of the queue directory (messages are stored in - this directory) + * The absolute path of the queue directory (messages are stored in this directory) * `Swift_DoctrineSpool`: - * The Doctrine model to use to store the messages (`MailMessage` by - default) + * The Doctrine model to use to store the messages (`MailMessage` by default) * The column name to use for message storage (`message` by default) - * The method to call to retrieve the messages to send (optional). It - receives the queue options as a argument. + * The method to call to retrieve the messages to send (optional). It receives the queue options as a argument. * `Swift_PropelSpool`: @@ -246,8 +217,7 @@ classes: * The column name to use for message storage (`message` by default) - * The method to call to retrieve the messages to send (optional). It - receives the queue options as a argument. + * The method to call to retrieve the messages to send (optional). It receives the queue options as a argument. Here is a classic configuration for a Doctrine spool: @@ -288,30 +258,25 @@ And the same configuration for a Propel spool: spool_class: Swift_PropelSpool spool_arguments: [ MailMessage, message, getSpooledMessages ] -To send the message stored in a queue, you can use the ~`project:send-emails`~ -task (note that this task is totally independent of the queue implementation, -and the options it takes): +To send the message stored in a queue, you can use the ~`project:send-emails`~ task (note that this task is totally independent of the queue implementation, and the options it takes): $ php symfony project:send-emails >**NOTE** >The `project:send-emails` task takes an `application` and `env` options. -When calling the `project:send-emails` task, email messages are sent via the -same transport as the one used for the `realtime` strategy. +When calling the `project:send-emails` task, email messages are sent via the same transport as the one used for the `realtime` strategy. >**TIP** ->Note that the `project:send-emails` task can be run on any machine, not ->necessarily on the machine that created the message. It works because ->everything is stored in the message object, even the file attachments. +>Note that the `project:send-emails` task can be run on any machine, not necessarily on the machine that created the message. +>It works because everything is stored in the message object, even the file attachments. - >**NOTE** ->The built-in implementation of the queues are very simple. They send emails ->without any error management, like they would have been sent if you have used ->the `realtime` strategy. Of course, the default queue classes can be extended ->to implement your own logic and error management. +>The built-in implementation of the queues are very simple. +>They send emails without any error management, like they would have been sent if you have used the `realtime` strategy. +>Of course, the default queue classes can be extended to implement your own logic and error management. The `project:send-emails` task takes two optional options: @@ -323,41 +288,32 @@ Both options can be combined: $ php symfony project:send-emails --message-limit=10 --time-limit=20 -The above command will stop sending messages when 10 messages are sent or -after 20 seconds. +The above command will stop sending messages when 10 messages are sent or after 20 seconds. -Even when using the `spool` strategy, you might need to send a message -immediately without storing it in the queue. This is possible by using the -special `sendNextImmediately()` method of the mailer: +Even when using the `spool` strategy, you might need to send a message immediately without storing it in the queue. +This is possible by using the special `sendNextImmediately()` method of the mailer: [php] $this->getMailer()->sendNextImmediately()->send($message); -In the previous example, the `$message` won't be stored in the queue and will -be sent immediately. As its name implies, the `sendNextImmediately()` method -only affects the very next message to be sent. +In the previous example, the `$message` won't be stored in the queue and will be sent immediately. +As its name implies, the `sendNextImmediately()` method only affects the very next message to be sent. >**NOTE** ->The `sendNextImmediately()` method has no special effect when the delivery ->strategy is not `spool`. +>The `sendNextImmediately()` method has no special effect when the delivery strategy is not `spool`. ### The ~`none`~ Strategy -This strategy is useful in the development environment to avoid emails to be -sent to real users. Messages are still available in the web debug toolbar -(more information in the section below about the mailer panel of the web debug -toolbar). +This strategy is useful in the development environment to avoid emails to be sent to real users. +Messages are still available in the web debug toolbar (more information in the section below about the mailer panel of the web debug toolbar). -It is also the best strategy for the test environment, where the -`sfTesterMailer` object allows you to introspect the messages without the need -to actually send them (more information in the section below about testing). +It is also the best strategy for the test environment, where the `sfTesterMailer` object allows you to introspect the messages without the need to actually send them (more information in the section below about testing). The Mail Transport ------------------ -Mail messages are actually sent by a transport. The transport is configured in -the `factories.yml` configuration file, and the default configuration uses the -SMTP server of the local machine: +Mail messages are actually sent by a transport. +The transport is configured in the `factories.yml` configuration file, and the default configuration uses the SMTP server of the local machine: [yml] transport: @@ -375,28 +331,21 @@ Swift Mailer comes bundled with three different transport classes: * ~`Swift_SendmailTransport`~: Uses `sendmail` to send messages. - * ~`Swift_MailTransport`~: Uses the native PHP `mail()` function to send - messages. + * ~`Swift_MailTransport`~: Uses the native PHP `mail()` function to send messages. >**TIP** ->The ["Transport Types"](http://swiftmailer.org/docs/transport-types) section ->of the Swift Mailer official documentation describes all you need to know ->about the built-in transport classes and their different parameters. +>The ["Transport Types"](http://swiftmailer.org/docs/transport-types) section of the Swift Mailer official documentation describes all you need to know about the built-in transport classes and their different parameters. Sending an Email from a Task ---------------------------- -Sending an email from a task is quite similar to sending an email from an -action, as the task system also provides a `getMailer()` method. +Sending an email from a task is quite similar to sending an email from an action, as the task system also provides a `getMailer()` method. When creating the mailer, the task system relies on the current configuration. -So, if you want to use a configuration from a specific application, you must -accept the `--application` option (see the chapter on tasks for more -information on this topic). +So, if you want to use a configuration from a specific application, you must accept the `--application` option (see the chapter on tasks for more information on this topic). -Notice that the task uses the same configuration as the controllers. So, if -you want to force the delivery when the `spool` strategy is used, use -`sendNextImmediately()`: +Notice that the task uses the same configuration as the controllers. +So, if you want to force the delivery when the `spool` strategy is used, use `sendNextImmediately()`: [php] $this->getMailer()->sendNextImmediately()->send($message); @@ -404,16 +353,14 @@ you want to force the delivery when the `spool` strategy is used, use Debugging --------- -Traditionally, debugging emails has been a nightmare. With symfony, it is very -easy, thanks to the ~web debug toolbar~. +Traditionally, debugging emails has been a nightmare. +With symfony, it is very easy, thanks to the ~web debug toolbar~. -From the comfort of your browser, you can easily and rapidly see how many -messages have been sent by the current action: +From the comfort of your browser, you can easily and rapidly see how many messages have been sent by the current action: ![Emails in the Web Debug Toolbar](http://www.symfony-project.org/images/more-with-symfony/emails_wdt.png "Emails in the Web Debug Toolbar") -If you click on the email icon, the sent messages are displayed in the panel -in their raw form as shown below. +If you click on the email icon, the sent messages are displayed in the panel in their raw form as shown below. ![Emails in the Web Debug Toolbar - details](http://www.symfony-project.org/images/more-with-symfony/emails_wdt_details.png "Emails in the Web Debug Toolbar - details") @@ -423,12 +370,9 @@ in their raw form as shown below. Testing ------- -Of course, the integration would not have been complete without a way to test -mail messages. By default, symfony registers a `mailer` tester -(~`sfMailerTester`~) to ease mail testing in functional tests. +Of course, the integration would not have been complete without a way to test mail messages. By default, symfony registers a `mailer` tester (~`sfMailerTester`~) to ease mail testing in functional tests. -The ~`hasSent()`~ method tests the number of messages sent during the current -request: +The ~`hasSent()`~ method tests the number of messages sent during the current request: [php] $browser-> @@ -439,8 +383,7 @@ request: The previous code checks that the `/foo` URL sends only one email. -Each sent email can be further tested with the help of the ~`checkHeader()`~ -and ~`checkBody()`~ methods: +Each sent email can be further tested with the help of the ~`checkHeader()`~ and ~`checkBody()`~ methods: [php] $browser-> @@ -452,19 +395,16 @@ and ~`checkBody()`~ methods: end() ; -The second argument of `checkHeader()` and the first argument of `checkBody()` -can be one of the following: +The second argument of `checkHeader()` and the first argument of `checkBody()` can be one of the following: * a string to check an exact match; * a regular expression to check the value against it; - * a negative regular expression (a regular expression starting with a `!`) to - check that the value does not match. + * a negative regular expression (a regular expression starting with a `!`) to check that the value does not match. -By default, the checks are done on the first message sent. If several messages -have been sent, you can choose the one you want to test with the -~`withMessage()`~ method: +By default, the checks are done on the first message sent. +If several messages have been sent, you can choose the one you want to test with the ~`withMessage()`~ method: [php] $browser-> @@ -477,12 +417,10 @@ have been sent, you can choose the one you want to test with the end() ; -The `withMessage()` takes a recipient as its first argument. It also takes a -second argument to indicate which message you want to test if several ones -have been sent to the same recipient. +The `withMessage()` takes a recipient as its first argument. +It also takes a second argument to indicate which message you want to test if several ones have been sent to the same recipient. -Last but not the least, the ~`debug()`~ method dumps the sent messages to spot -problems when a test fails: +Last but not the least, the ~`debug()`~ method dumps the sent messages to spot problems when a test fails: [php] $browser-> @@ -494,21 +432,15 @@ problems when a test fails: Email Messages as Classes ------------------------- -In this chapter's introduction, you have learnt how to send emails from an -action. This is probably the easiest way to send emails in a symfony -application and probably the best when you just need to send a few simple -messages. +In this chapter's introduction, you have learnt how to send emails from an action. +This is probably the easiest way to send emails in a symfony application and probably the best when you just need to send a few simple messages. -But when your application needs to manage a large number of different email -messages, you should probably have a different strategy. +But when your application needs to manage a large number of different email messages, you should probably have a different strategy. >**NOTE** ->As an added bonus, using classes for email messages means that the same email ->message can be used in different applications; a frontend and a backend one for ->instance. +>As an added bonus, using classes for email messages means that the same email message can be used in different applications; a frontend and a backend one for instance. -As messages are plain PHP objects, the obvious way to organize your messages -is to create one class for each of them: +As messages are plain PHP objects, the obvious way to organize your messages is to create one class for each of them: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -525,14 +457,12 @@ is to create one class for each of them: } } -Sending a message from an action, or from anywhere else for that matter, is -simple a matter of instantiating the right message class: +Sending a message from an action, or from anywhere else for that matter, is simple a matter of instantiating the right message class: [php] $this->getMailer()->send(new ProjectConfirmationMessage()); -Of course, adding a base class to centralize the shared headers like the -`From` header, or to add a common signature can be convenient: +Of course, adding a base class to centralize the shared headers like the `From` header, or to add a common signature can be convenient: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -565,8 +495,7 @@ Of course, adding a base class to centralize the shared headers like the } } -If a message depends on some model objects, you can of course pass them as -arguments to the constructor: +If a message depends on some model objects, you can of course pass them as arguments to the constructor: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -583,8 +512,7 @@ Recipes ### Sending Emails via ~Gmail~ -If you don't have an SMTP server but have a Gmail account, use the following -configuration to use the Google servers to send and archive messages: +If you don't have an SMTP server but have a Gmail account, use the following configuration to use the Google servers to send and archive messages: [yml] transport: @@ -596,16 +524,13 @@ configuration to use the Google servers to send and archive messages: username: your_gmail_username_goes_here password: your_gmail_password_goes_here -Replace the `username` and `password` with your Gmail credentials and you are -done. +Replace the `username` and `password` with your Gmail credentials and you are done. ### Customizing the Mailer Object -If configuring the mailer via the `factories.yml` is not enough, you can -listen to the ~`mailer.configure`~ event, and further customize the mailer. +If configuring the mailer via the `factories.yml` is not enough, you can listen to the ~`mailer.configure`~ event, and further customize the mailer. -You can connect to this event in your `ProjectConfiguration` class like shown -below: +You can connect to this event in your `ProjectConfiguration` class like shown below: [php] class ProjectConfiguration extends sfProjectConfiguration @@ -632,8 +557,7 @@ The following section illustrates a powerful usage of this technique. ### Using ~Swift Mailer Plugins~ -To use Swift Mailer plugins, listen to the `mailer.configure` event (see the -section above): +To use Swift Mailer plugins, listen to the `mailer.configure` event (see the section above): [php] public function configureMailer(sfEvent $event) @@ -648,17 +572,13 @@ section above): } >**TIP** ->The ["Plugins"](http://swiftmailer.org/docs/plugins) section of the ->Swift Mailer official documentation describes all you need to know about the ->built-in plugins. +>The ["Plugins"](http://swiftmailer.org/docs/plugins) section of the Swift Mailer official documentation describes all you need to know about the built-in plugins. ### Customizing the Spool Behavior -The built-in implementation of the spools is very simple. Each spool retrieves -all emails from the queue in a random order and sends them. +The built-in implementation of the spools is very simple. Each spool retrieves all emails from the queue in a random order and sends them. -You can configure a spool to limit the time spent to send emails (in seconds), -or to limit the number of messages to send: +You can configure a spool to limit the time spent to send emails (in seconds), or to limit the number of messages to send: [php] $spool = $mailer->getSpool(); @@ -666,9 +586,8 @@ or to limit the number of messages to send: $spool->setMessageLimit(10); $spool->setTimeLimit(10); -In this section, you will learn how to implement a priority system for the -queue. It will give you all the information needed to implement your own -logic. +In this section, you will learn how to implement a priority system for the queue. +It will give you all the information needed to implement your own logic. First, add a `priority` column to the schema: @@ -695,8 +614,7 @@ When sending an email, set the priority header (1 means highest): ; $this->getMailer()->send($message); -Then, override the default `setMessage()` method to change the priority of the -`MailMessage` object itself: +Then, override the default `setMessage()` method to change the priority of the `MailMessage` object itself: [php] // for Propel @@ -723,9 +641,7 @@ Then, override the default `setMessage()` method to change the priority of the } } -Notice that the message is serialized by the queue, so it has to be unserialized -before getting the priority value. Now, create a method that orders the -messages by priority: +Notice that the message is serialized by the queue, so it has to be unserialized before getting the priority value. Now, create a method that orders the messages by priority: [php] // for Propel @@ -754,23 +670,19 @@ messages by priority: // ... } -The last step is to define the retrieval method in the `factories.yml` -configuration to change the default way in which the messages are obtained -from the queue: +The last step is to define the retrieval method in the `factories.yml` configuration to change the default way in which the messages are obtained from the queue: [yml] spool_arguments: [ MailMessage, message, getSpooledMessages ] -That's all there is to it. Now, each time you run the `project:send-emails` -task, each email will be sent according to its priority. +That's all there is to it. +Now, each time you run the `project:send-emails` task, each email will be sent according to its priority. >**SIDEBAR** >Customizing the Spool with any Criteria > ->The previous example uses a standard message header, the priority. But if you ->want to use any criteria, or if you don't want to alter the sent message, ->you can also store the criteria as a custom header, and remove it before ->sending the email. +>The previous example uses a standard message header, the priority. +>But if you want to use any criteria, or if you don't want to alter the sent message, you can also store the criteria as a custom header, and remove it before sending the email. > >First, add a custom header to the message to be sent: > @@ -786,8 +698,7 @@ task, each email will be sent according to its priority. > $this->getMailer()->send($message); > } > ->Then, retrieve the value from this header when storing the message in the ->queue, and remove it immediately: +>Then, retrieve the value from this header when storing the message in the queue, and remove it immediately: > > [php] > public function setMessage($message) From 69e3fb30fcdfe2cefd0ba2e974f81cd399e6f390 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sat, 25 Sep 2010 03:38:29 -0700 Subject: [PATCH 07/14] --- more-with-symfony/ru/04-Emails.markdown | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index c75fd3f..def9906 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -39,7 +39,7 @@ From an action, retrieving the mailer instance is made simple with the `getMaile [php] $mailer = $this->getMailer(); -### Наибыстрейший путь +### Быстрый способ Отправка электронной столь же проста, как и использование метода ~`sfMailer::composeAndSend()`~: @@ -64,8 +64,7 @@ Whenever a method takes an email address as a parameter, you can pass a string o $address = 'fabien@example.com'; $address = array('fabien@example.com' => 'Fabien Potencier'); -Of course, you can send an email to several people at once by passing an array -of emails as the second argument of the method: +Of course, you can send an email to several people at once by passing an array of emails as the second argument of the method: [php] $to = array( @@ -95,7 +94,7 @@ This is useful, for instance, when you need to add an ~attachment|email attachme // send the message $this->getMailer()->send($message); -### The Powerful Way +### Мощный способ You can also create a message object directly for even more flexibility: @@ -111,9 +110,9 @@ You can also create a message object directly for even more flexibility: $this->getMailer()->send($message); >**TIP** ->The ["Creating Messages"](http://swiftmailer.org/docs/messages) and ["Message Headers"](http://swiftmailer.org/docs/headers) sections of the Swift Mailer official documentation describe all you need to know about creating messages. +>Разделы официальной документации по Swift Mailer ["Creating Messages"](http://swiftmailer.org/docs/messages) (создание сообщений) и ["Message Headers"](http://swiftmailer.org/docs/headers) (заголовки сообщений) описывают всё, что вам необходимо знать о создании сообщений. -### Using the Symfony View +### Использование Вида Sending your emails from your actions allows you to leverage the power of partials and components quite easily. From 3762eb989a9326c7f65940546a8485f0fa6bd34c Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sat, 25 Sep 2010 06:00:29 -0700 Subject: [PATCH 08/14] --- more-with-symfony/ru/04-Emails.markdown | 118 ++++++++++++------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index def9906..0953ade 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -1,5 +1,5 @@ -Emails -====== +Электронная почта +================= *by Fabien Potencier* @@ -34,7 +34,7 @@ dedicated [documentation](http://www.swiftmailer.org/docs). Отправка электронной почты из действия -------------------------------------- -From an action, retrieving the mailer instance is made simple with the `getMailer()` shortcut method: +В действии, получение экземпляра почтового класса реализовано через метод `getMailer()`: [php] $mailer = $this->getMailer(); @@ -58,13 +58,13 @@ From an action, retrieving the mailer instance is made simple with the `getMaile * тема письма; * тело письма. -Whenever a method takes an email address as a parameter, you can pass a string or an array: +Адреса можно передавать в виде массивов или строк: [php] $address = 'fabien@example.com'; $address = array('fabien@example.com' => 'Fabien Potencier'); -Of course, you can send an email to several people at once by passing an array of emails as the second argument of the method: +Естественно, вы можете отправить письмо нескольким получателям, передав их адреса массивом во втором параметре метода: [php] $to = array( @@ -81,22 +81,22 @@ Of course, you can send an email to several people at once by passing an array o ### Гибкий способ -Если вам требуется большая гибкость, для создания сообщения вы можете использовать метод ~`sfMailer::compose()`~, customize it the way you want, and eventually send it. -This is useful, for instance, when you need to add an ~attachment|email attachment~ as shown below: +Если вам требуется большая гибкость, для создания сообщения вы можете использовать метод ~`sfMailer::compose()`~, это позволит вам настроить его необходимым образом, а затем отправить. +Это может быть полезно, когда, например, вам требуется добавить к письму вложение: [php] - // create a message object + // создание объекта сообщения $message = $this->getMailer() ->compose('from@example.com', 'fabien@example.com', 'Subject', 'Body') ->attach(Swift_Attachment::fromPath('/path/to/a/file.zip')) ; - // send the message + // отправка сообщения $this->getMailer()->send($message); ### Мощный способ -You can also create a message object directly for even more flexibility: +Также вы можете напрямую создать объект сообщения, что даст ещё большую гибкость: [php] $message = Swift_Message::newInstance() @@ -110,19 +110,19 @@ You can also create a message object directly for even more flexibility: $this->getMailer()->send($message); >**TIP** ->Разделы официальной документации по Swift Mailer ["Creating Messages"](http://swiftmailer.org/docs/messages) (создание сообщений) и ["Message Headers"](http://swiftmailer.org/docs/headers) (заголовки сообщений) описывают всё, что вам необходимо знать о создании сообщений. +>Разделы официальной документации по Swift Mailer ["Creating Messages"](http://swiftmailer.org/docs/messages) (создание сообщений) и ["Message Headers"](http://swiftmailer.org/docs/headers) (заголовки сообщений) описывают всё, что вам необходимо знать о процессе создания сообщений. ### Использование Вида -Sending your emails from your actions allows you to leverage the power of partials and components quite easily. +Отправка электронных сообщений из действий позволяет вам легко использовать всю мощь компонентов. [php] $message->setBody($this->getPartial('partial_name', $arguments)); -Configuration -------------- +Конфигурация +------------ -As any other symfony factory, the mailer can be configured in the `factories.yml` configuration file. The default configuration reads as follows: +Как и другие фабрики в symfony, почта может быть сконфигурирована через файл `factories.yml`. По умолчанию, конфигурация выглядит следующим образом: [yml] mailer: @@ -140,7 +140,7 @@ As any other symfony factory, the mailer can be configured in the `factories.yml username: ~ password: ~ -When creating a new application, the local `factories.yml` configuration file overrides the default configuration with some sensible defaults for the `prod`, `env`, and `test` environments: +При создании нового приложения, локальный файл `factories.yml` приложения переопределяет некоторые заданные по умолчанию значения для окружений `prod`, `env` и `test`: [yml] test: @@ -153,75 +153,75 @@ When creating a new application, the local `factories.yml` configuration file ov param: delivery_strategy: none -The Delivery Strategy ---------------------- +Стратегия доставки +------------------ -One of the most useful feature of the Swift Mailer integration in symfony is the delivery strategy. -The delivery strategy allows you to tell symfony how to deliver email messages and is configured via the ~`delivery_strategy`~ setting of `factories.yml`. -The strategy changes the way the ~`send()`|`sfMailer::send()`~ method behaves. Four strategies are available by default, which should suit all the common needs: +Одна из самых часто используемых опций почтового сервиса Swift Mailer в symfony - управление стратегией доставки. +Стратегия доставки указывает symfony как именно следует доставлять сообщения, и её можно задать через настройку ~`delivery_strategy`~ в `factories.yml`. +Стратегия изменяет поведение метода ~`sfMailer::send()`~. По умолчанию доступно четыре стратегии, которые описывают все общие случаи: - * `realtime`: Messages are sent in realtime. - * `single_address`: Messages are sent to a single address. - * `spool`: Messages are stored in a queue. - * `none`: Messages are simply ignored. + * `realtime` - сообщения отправляются в реальном времени; + * `single_address` - сообщения отправляются на один адрес; + * `spool` - сообщения помещаются в очередь; + * `none` - сообщения просто игнорируются. -### The ~`realtime`~ Strategy +### Стратегия ~`realtime`~ -The `realtime` strategy is the default delivery strategy, and the easiest to setup as there is nothing special to do. +Стратегия `realtime` является стратегией по умолчанию, и не требует никаких специальных настроек. -Email messages are sent via the transport configured in the `transport` section of the `factories.yml` configuration file (see the next section for more information about how to configure the mail transport). +Сообщения электронной почты отправляются через транспорт, указанный в разделе `transport` конфигурационного файла `factories.yml` (см. следующий раздел, для получения информации о конфигурировании почтового транспорта). -### The ~`single_address`~ Strategy +### Стратегия ~`single_address`~ -With the `single_address` strategy, all messages are sent to a single address, configured via the `delivery_address` setting. +При указании стратегии `single_address`, все сообщения посылаются на один адрес, заданный параметром `delivery_address`. -This strategy is really useful in the development environment to avoid sending messages to real users, but still allow the developer to check the rendered message in an email reader. +Эта стратегия очень полезна в отладочном окружении, она исключает отправку сообщений реальным пользователям, но всё ещё позволяет разработчику проверять корректность вывода сообщений. >**TIP** ->If you need to verify the original `to`, `cc`, and `bcc` recipients, they are available as values of the following headers: `X-Swift-To`, `X-Swift-Cc`, and `X-Swift-Bcc` respectively. +>Если вам потребуется проверить оригинальных получателей `to`, `cc` и `bcc`, то они доступны через значения следующих заголовков: `X-Swift-To`, `X-Swift-Cc` и `X-Swift-Bcc`. -Email messages are sent via the same email transport as the one used for the `realtime` strategy. +Сообщения электронной почты отправляются через тот же самый транспорт, который используется для стратегии `realtime`. -### The ~`spool`~ Strategy +### Стратегия `spool`~ -With the `spool` strategy, messages are stored in a queue. +При указании стратегии `spool`, сообщения помещаются в очередь. -This is the best strategy for the production environment, as web requests do not wait for the emails to be sent. +Это наилучшая стратегия для рабочего окружения, так как обработчик вэб-запросов не ждёт реальной отправки сообщений. -The `spool` class is configured with the ~`spool_class`~ setting. By default, symfony comes bundled with three of them: +Класс для очереди задаётся параметром ~`spool_class`~. По умолчанию, symfony поставляется с тремя такими классами: - * ~`Swift_FileSpool`~: Messages are stored on the filesystem. + * ~`Swift_FileSpool`~ - сообщения сохраняются в файловой системе. - * ~`Swift_DoctrineSpool`~: Messages are stored in a Doctrine model. + * ~`Swift_DoctrineSpool`~ - сообщения сохраняются в Doctrine-модели. - * ~`Swift_PropelSpool`~: Messages are stored in a Propel model. + * ~`Swift_PropelSpool`~ - сообщения сохранятся в Propel-модели. -When the spool is instantiated, the ~`spool_arguments`~ setting is used as the constructor arguments. Here are the options available for the built-in queues classes: +Когда создаётся экземпляр очереди, опция ~`spool_arguments`~ используется для передачи аргументов в конструктор. Вот опции, которые используются для встроенных классов: * `Swift_FileSpool`: - * The absolute path of the queue directory (messages are stored in this directory) + * Абсолютный путь к каталогу очереди (сообщения сохраняются в эту директорию) * `Swift_DoctrineSpool`: - * The Doctrine model to use to store the messages (`MailMessage` by default) + * Doctrine-модель, используемая для хранения сообщений (по умолчанию `MailMessage`) - * The column name to use for message storage (`message` by default) + * Имя столбца для сохранения сообщения (по умолчанию `message`) - * The method to call to retrieve the messages to send (optional). It receives the queue options as a argument. + * Метод, который будет использоваться для получения сообщения из очереди и его отправки (опционально). В качестве аргумента он получает опции очереди. * `Swift_PropelSpool`: - * The Propel model to use to store the messages (`MailMessage` by default) + * Propel-модель, используемая для хранения сообщений (по умолчанию `MailMessage`) - * The column name to use for message storage (`message` by default) + * Имя столбца для сохранения сообщения (по умолчанию `message`) - * The method to call to retrieve the messages to send (optional). It receives the queue options as a argument. + * Метод, который будет использоваться для получения сообщения из очереди и его отправки (опционально). В качестве аргумента он получает опции очереди. -Here is a classic configuration for a Doctrine spool: +Это классическая конфигурация для Doctrine-очереди: [yml] - # Schema configuration in schema.yml + # схема в schema.yml MailMessage: actAs: { Timestampable: ~ } columns: @@ -230,7 +230,7 @@ Here is a classic configuration for a Doctrine spool: - [yml] - # configuration in factories.yml + # конфигурация в factories.yml mailer: class: sfMailer param: @@ -238,10 +238,10 @@ Here is a classic configuration for a Doctrine spool: spool_class: Swift_DoctrineSpool spool_arguments: [ MailMessage, message, getSpooledMessages ] -And the same configuration for a Propel spool: +И такая же конфигурация для Propel-очереди: [yml] - # Schema configuration in schema.yml + # схема в schema.yml mail_message: message: { type: blob, required: true } created_at: ~ @@ -249,7 +249,7 @@ And the same configuration for a Propel spool: - [yml] - # configuration in factories.yml + # конфигурация в factories.yml dev: mailer: param: @@ -257,18 +257,18 @@ And the same configuration for a Propel spool: spool_class: Swift_PropelSpool spool_arguments: [ MailMessage, message, getSpooledMessages ] -To send the message stored in a queue, you can use the ~`project:send-emails`~ task (note that this task is totally independent of the queue implementation, and the options it takes): +Для отправки сохранённых в очереди сообщений, вы можете использовать задачу ~`project:send-emails`~ (заметьте, эта задача полностью независима от реализации очереди и её опций): $ php symfony project:send-emails >**NOTE** ->The `project:send-emails` task takes an `application` and `env` options. +>Для задачи `project:send-emails` можно указывать опции `application` и `env`. -When calling the `project:send-emails` task, email messages are sent via the same transport as the one used for the `realtime` strategy. +Когда вызывается задача `project:send-emails`, сообщения электронной почты посылаются через тот же транспорт, который используется для стратегии `realtime`. >**TIP** ->Note that the `project:send-emails` task can be run on any machine, not necessarily on the machine that created the message. ->It works because everything is stored in the message object, even the file attachments. +>Заметьте, задача `project:send-emails` может быть запущена на любой машине, а не обязательно на той, на которой было создано сообщение. +>Это происходит из-за того, что всё сохраняется в объекте сообщения (даже файловые вложения). - From 59b76c00fc51a58d1348749f7c1298e6355a9ae3 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:16:50 -0700 Subject: [PATCH 09/14] 100% translated & tested --- more-with-symfony/ru/04-Emails.markdown | 242 ++++++++++++------------ 1 file changed, 119 insertions(+), 123 deletions(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index 0953ade..b8e8cd5 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -3,11 +3,9 @@ *by Fabien Potencier* -Sending ~emails~ with symfony is simple and powerful, thanks to the usage of -the [Swift Mailer](http://www.swiftmailer.org/) library. Although ~Swift Mailer~ -makes sending emails easy, symfony provides a thin wrapper on top of it to -make sending emails even more flexible and powerful. This chapter will teach -you how to put all the power at your disposal. +Благодаря использованию библиотеки [Swift Mailer](http://www.swiftmailer.org/), отправка электронных писем в symfony выполняется очень просто. +Хотя всю работу делает ~Swift Mailer~, symfony выступает в роли "обёртки", которая позволяет сделать процесс отправки писем более гибким и мощным. +В этой главе вы научитесь использовать всю эту мощь. >**NOTE** >symfony 1.3 включает Swift Mailer версии 4.1. @@ -15,33 +13,30 @@ you how to put all the power at your disposal. Введение -------- -Email management in symfony is centered around a mailer object. And like many -other core symfony objects, the mailer is a factory. It is configured in the -`factories.yml` configuration file, and always available via the context -instance: +Управление электронной почтой в symfony сконцентрировано в специальном почтовом объекте (мейлере). +Как и многие другие symfony-объекты, этот объект является фабрикой. Он настраивается через конфигурационный файл `factories.yml`, и всегда доступен через экземпляр контекста: [php] $mailer = sfContext::getInstance()->getMailer(); >**TIP** ->Unlike other factories, the mailer is loaded and initialized on demand. If ->you don't use it, there is no performance impact whatsoever. +>В отличие от других фабрик, почтовый объект загружается и инициализируется по необходимости. +>Если вы его не используете, то и никакого воздействия на общую производительность не происходит. -This tutorial explains the Swift Mailer integration in symfony. If you want to -learn the nitty-gritty details of the Swift Mailer library itself, refer to its -dedicated [documentation](http://www.swiftmailer.org/docs). +Эта глава объясняет интеграцию Swift Mailer в symfony. +Если вы хотите узнать больше о самой библиотеке Swift Mailer, обратитесь к её [документации](http://www.swiftmailer.org/docs). Отправка электронной почты из действия -------------------------------------- -В действии, получение экземпляра почтового класса реализовано через метод `getMailer()`: +В действии получение экземпляра почтового класса реализовано через метод `getMailer()`: [php] $mailer = $this->getMailer(); ### Быстрый способ -Отправка электронной столь же проста, как и использование метода ~`sfMailer::composeAndSend()`~: +Отправка электронной почты столь же проста, как и использование метода ~`sfMailer::composeAndSend()`~: [php] $this->getMailer()->composeAndSend( @@ -157,8 +152,8 @@ dedicated [documentation](http://www.swiftmailer.org/docs). ------------------ Одна из самых часто используемых опций почтового сервиса Swift Mailer в symfony - управление стратегией доставки. -Стратегия доставки указывает symfony как именно следует доставлять сообщения, и её можно задать через настройку ~`delivery_strategy`~ в `factories.yml`. -Стратегия изменяет поведение метода ~`sfMailer::send()`~. По умолчанию доступно четыре стратегии, которые описывают все общие случаи: +Стратегия указывает symfony, как именно следует доставлять сообщения, и её можно задать через настройку ~`delivery_strategy`~ в `factories.yml`. +Стратегия изменяет поведение метода ~`sfMailer::send()`~. По умолчанию доступно четыре стратегии, которые подходят для всех общих случаев: * `realtime` - сообщения отправляются в реальном времени; * `single_address` - сообщения отправляются на один адрес; @@ -268,51 +263,51 @@ dedicated [documentation](http://www.swiftmailer.org/docs). >**TIP** >Заметьте, задача `project:send-emails` может быть запущена на любой машине, а не обязательно на той, на которой было создано сообщение. ->Это происходит из-за того, что всё сохраняется в объекте сообщения (даже файловые вложения). +>Это происходит из-за того, что все связанные с сообщением данные сохраняется в объекте сообщения (даже файловые вложения). - >**NOTE** ->The built-in implementation of the queues are very simple. ->They send emails without any error management, like they would have been sent if you have used the `realtime` strategy. ->Of course, the default queue classes can be extended to implement your own logic and error management. +>Встроенные реализации очереди очень просты. +>Они посылают электронную почту без какой-либо обработки ошибок, также как если бы вы использовали стратегию `realtime`. +>Конечно, вы можете расширить классы очередей, чтобы реализовать свою собственную логику обработки ошибок. -The `project:send-emails` task takes two optional options: +Задача `project:send-emails` поддерживает две необязательные опции: - * `message-limit`: Limits the number of messages to sent. + * `message-limit` - ограничение на количество посылаемых сообщений. - * `time-limit`: Limits the time spent to send messages (in seconds). + * `time-limit` - ограничение на время, затрачиваемое на отправку сообщений (в секундах). -Both options can be combined: +Опции можно комбинировать: $ php symfony project:send-emails --message-limit=10 --time-limit=20 -The above command will stop sending messages when 10 messages are sent or after 20 seconds. +Эта команда прекратит посылать сообщения либо после отправки 10-го, либо через 20 секунд. -Even when using the `spool` strategy, you might need to send a message immediately without storing it in the queue. -This is possible by using the special `sendNextImmediately()` method of the mailer: +Даже если вы используете стратегию `spool`, вам может потребоваться отправить сообщение немедленно без его помещения в очередь. +Это можно реализовать через специальный метод `sendNextImmediately()`: [php] $this->getMailer()->sendNextImmediately()->send($message); -In the previous example, the `$message` won't be stored in the queue and will be sent immediately. -As its name implies, the `sendNextImmediately()` method only affects the very next message to be sent. +В данном примере, `$message` не запоминается в очереди и отправляется немедленно. +Как можно догадаться по имени метода, `sendNextImmediately()` воздействует только на следующее отправляемое сообщение. >**NOTE** >The `sendNextImmediately()` method has no special effect when the delivery strategy is not `spool`. -### The ~`none`~ Strategy +### Стратегия ~`none`~ -This strategy is useful in the development environment to avoid emails to be sent to real users. -Messages are still available in the web debug toolbar (more information in the section below about the mailer panel of the web debug toolbar). +Эта стратегия полезна в окружении разработки (отладочном), она позволяет отключить отправку сообщений реальным пользователям. +Сообщения всё ещё доступны через отладочную вэб-панель (больше информации об этом свойстве приведено в разделе, посвящённом взаимодействию с отладочной панелью). -It is also the best strategy for the test environment, where the `sfTesterMailer` object allows you to introspect the messages without the need to actually send them (more information in the section below about testing). +Также эта стратегия является лучшей для тестового окружения, где объект `sfTesterMailer` позволяет вам исследовать сообщения без их отправки (больше информации об этом свойстве приведено в разделе, посвящённом тестированию). -The Mail Transport +Почтовый транспорт ------------------ -Mail messages are actually sent by a transport. -The transport is configured in the `factories.yml` configuration file, and the default configuration uses the SMTP server of the local machine: +Почтовые сообщения фактически посылает транспорт. +Транспорт настраивается через конфигурационный файл `factories.yml`, и конфигурация по умолчанию использует SMTP-сервер локальной машины: [yml] transport: @@ -324,54 +319,54 @@ The transport is configured in the `factories.yml` configuration file, and the d username: ~ password: ~ -Swift Mailer comes bundled with three different transport classes: +Swift Mailer имеет три встроенных класса транспорта: - * ~`Swift_SmtpTransport`~: Uses a SMTP server to send messages. + * ~`Swift_SmtpTransport`~ - для отправки сообщений используется SMTP-сервер. - * ~`Swift_SendmailTransport`~: Uses `sendmail` to send messages. + * ~`Swift_SendmailTransport`~: для отправки сообщений используется `sendmail`. - * ~`Swift_MailTransport`~: Uses the native PHP `mail()` function to send messages. + * ~`Swift_MailTransport`~ - для отправки сообщений используется PHP-функция `mail()`. >**TIP** ->The ["Transport Types"](http://swiftmailer.org/docs/transport-types) section of the Swift Mailer official documentation describes all you need to know about the built-in transport classes and their different parameters. +>Глава ["Transport Types (Типы Транспортов)"](http://swiftmailer.org/docs/transport-types) официальной документации Swift Mailer описывает всё, что вам необходимо знать о встроенных классах транспорта и их параметрах. -Sending an Email from a Task ----------------------------- +Отправка электронной почты из задачи +------------------------------------ -Sending an email from a task is quite similar to sending an email from an action, as the task system also provides a `getMailer()` method. +Отправка электронной почты из задачи подобна отправке из действия, так как система управления задачами поддерживает метод `getMailer()`. -When creating the mailer, the task system relies on the current configuration. -So, if you want to use a configuration from a specific application, you must accept the `--application` option (see the chapter on tasks for more information on this topic). +При создании почтового объекта система управления задачами полагается на текущую конфигурацию. +Таким образом, если вы хотите использовать конфигурацию определённого приложения, то вам необходимо указывать опцию `--application` (см. главу про задачи для получения дополнительной информации по этой теме). -Notice that the task uses the same configuration as the controllers. -So, if you want to force the delivery when the `spool` strategy is used, use `sendNextImmediately()`: +Заметьте, что задача использует ту же самую конфигурацию, что и контроллеры. +Так, если вы хотите форсировать отправку при использовании стратегии `spool`, используйте метод `sendNextImmediately()`: [php] $this->getMailer()->sendNextImmediately()->send($message); -Debugging ---------- +Отладка +------- -Traditionally, debugging emails has been a nightmare. -With symfony, it is very easy, thanks to the ~web debug toolbar~. +Традиционно, отладка электронных писем была ночным кошмаром разработчика. +С symfony ситуация сильно упростилась, благодаря ~отладочной вэб-панели~. -From the comfort of your browser, you can easily and rapidly see how many messages have been sent by the current action: +В своём браузере вы быстро можете увидеть количество отправленных текущим действием сообщений: -![Emails in the Web Debug Toolbar](http://www.symfony-project.org/images/more-with-symfony/emails_wdt.png "Emails in the Web Debug Toolbar") +![Электронная почта и отладочная вэб-панель](http://www.symfony-project.org/images/more-with-symfony/emails_wdt.png "Электронная почта в отладочной вэб-панели") -If you click on the email icon, the sent messages are displayed in the panel in their raw form as shown below. +Если вы кликните по иконке с конвертом, отправленные сообщения отобразятся в панели. -![Emails in the Web Debug Toolbar - details](http://www.symfony-project.org/images/more-with-symfony/emails_wdt_details.png "Emails in the Web Debug Toolbar - details") +![Электронная почта и отладочная вэб-панель - детали](http://www.symfony-project.org/images/more-with-symfony/emails_wdt_details.png "Детализация электронной почты в отладочной вэб-панели") >**NOTE** ->Each time an email is sent, symfony also adds a message in the log. +>Каждый раз при оправке сообщения, symfony также добавляет сообщение в лог. -Testing -------- +Тестирование +------------ -Of course, the integration would not have been complete without a way to test mail messages. By default, symfony registers a `mailer` tester (~`sfMailerTester`~) to ease mail testing in functional tests. +Конечно, интеграция была бы неполной без способа проверки почтовых сообщений. По умолчанию, для упрощения проверки почтовых сообщений в функциональных тестах symfony регистрирует тестер `mailer` (~`sfMailerTester`~). -The ~`hasSent()`~ method tests the number of messages sent during the current request: +Метод ~`hasSent()`~ проверяет количество сообщений, отправленных в ходе текущего запроса: [php] $browser-> @@ -380,9 +375,9 @@ The ~`hasSent()`~ method tests the number of messages sent during the current re hasSent(1) ; -The previous code checks that the `/foo` URL sends only one email. +Этот код проверяет, что для URL `/foo` отправлено только одно сообщение. -Each sent email can be further tested with the help of the ~`checkHeader()`~ and ~`checkBody()`~ methods: +Каждое отправленное электронное письмо может быть последовательно проверено с помощью методов ~`checkHeader()`~ и ~`checkBody()`~: [php] $browser-> @@ -394,16 +389,16 @@ Each sent email can be further tested with the help of the ~`checkHeader()`~ and end() ; -The second argument of `checkHeader()` and the first argument of `checkBody()` can be one of the following: +Вторым аргументом в `checkHeader()` и первым в `checkBody()` может быть одно из следующего: - * a string to check an exact match; + * строка для проверки полного соответствия; - * a regular expression to check the value against it; + * регулярное выражение для проверки конкретных значений; - * a negative regular expression (a regular expression starting with a `!`) to check that the value does not match. + * отрицательное регулярное выражение (регулярное выражение, которое начинается с символа `!`) для проверки значения на несоответствие. -By default, the checks are done on the first message sent. -If several messages have been sent, you can choose the one you want to test with the ~`withMessage()`~ method: +По умолчанию, проверяется успешность отправки первого сообщения. +Если посылается несколько сообщений, вы можете выбрать конкретное сообщение с помощью метода ~`withMessage()`~: [php] $browser-> @@ -416,10 +411,10 @@ If several messages have been sent, you can choose the one you want to test with end() ; -The `withMessage()` takes a recipient as its first argument. -It also takes a second argument to indicate which message you want to test if several ones have been sent to the same recipient. +Метод `withMessage()` в качестве первого аргумента принимает адрес получателя. +Также этот метод принимает второй аргумент, указывающий номер сообщения в случае отправки нескольких сообщений одному и тому же получателю. -Last but not the least, the ~`debug()`~ method dumps the sent messages to spot problems when a test fails: +Последний, но не менее важный метод, ~`debug()`~, выводит сообщения для изучения при провале тестирования: [php] $browser-> @@ -428,18 +423,18 @@ Last but not the least, the ~`debug()`~ method dumps the sent messages to spot p debug() ; -Email Messages as Classes -------------------------- +Сообщения электронной почты как классы +-------------------------------------- -In this chapter's introduction, you have learnt how to send emails from an action. -This is probably the easiest way to send emails in a symfony application and probably the best when you just need to send a few simple messages. +Во введении к этой главе вы узнали, как послать электронное письмо из действия. +Вероятно, это самый простой и лучший путь для отправки письма в symfony-приложении. -But when your application needs to manage a large number of different email messages, you should probably have a different strategy. +Но когда приложению необходимо управлять большим числом разных сообщений, вам потребуется использовать несколько иную стратегию. >**NOTE** ->As an added bonus, using classes for email messages means that the same email message can be used in different applications; a frontend and a backend one for instance. +>Как дополнительный бонус, использование классов для почтовых сообщений позволяет использовать одно и то же сообщение в разных приложениях, например во frontend и backend. -As messages are plain PHP objects, the obvious way to organize your messages is to create one class for each of them: +Поскольку сообщения - простые PHP-объекты, очевидный способ организовать их состоит в создании для них отдельных классов: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -456,12 +451,12 @@ As messages are plain PHP objects, the obvious way to organize your messages is } } -Sending a message from an action, or from anywhere else for that matter, is simple a matter of instantiating the right message class: +Отправка сообщения из действия или откуда-либо ещё, заключается в выборе правильного класса сообщения: [php] $this->getMailer()->send(new ProjectConfirmationMessage()); -Of course, adding a base class to centralize the shared headers like the `From` header, or to add a common signature can be convenient: +Естественно, удобно использовать некий базовый класс для управления общими заголовками (например, `From`) или добавления общей для всех сообщений подписи: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -484,17 +479,17 @@ Of course, adding a base class to centralize the shared headers like the `From` $body .= <<setFrom(array('app@example.com' => 'My App Bot')); + $this->setFrom(array('app@example.com' => 'Бот My App')); } } -If a message depends on some model objects, you can of course pass them as arguments to the constructor: +Если сообщение зависит от объектов некоторой модели, вы можете передать их в качестве аргументов конструктора: [php] // lib/email/ProjectConfirmationMessage.class.php @@ -502,16 +497,16 @@ If a message depends on some model objects, you can of course pass them as argum { public function __construct($user) { - parent::__construct('Confirmation for '.$user->getName(), 'Body'); + parent::__construct('Подтверждение регистрации пользователя '.$user->getName(), 'Body'); } } -Recipes +Рецепты ------- -### Sending Emails via ~Gmail~ +### Отправка почты через ~Gmail~ -If you don't have an SMTP server but have a Gmail account, use the following configuration to use the Google servers to send and archive messages: +Если у вас нет собственного SMTP-сервера, но есть аккаунт на Gmail, использование следующей конфигурации позволит вам отправлять и архивировать почту через сервера Google: [yml] transport: @@ -520,16 +515,16 @@ If you don't have an SMTP server but have a Gmail account, use the following con host: smtp.gmail.com port: 465 encryption: ssl - username: your_gmail_username_goes_here - password: your_gmail_password_goes_here + username: ваш_логин_на_gmail + password: ваш_пароль_на_gmail -Replace the `username` and `password` with your Gmail credentials and you are done. +Замените значения для `username` и `password` на ваши учётные данные Gmail. -### Customizing the Mailer Object +### Настройка почтового объекта -If configuring the mailer via the `factories.yml` is not enough, you can listen to the ~`mailer.configure`~ event, and further customize the mailer. +Если вам недостаточны возможности конфигурирования почтового объекта через `factories.yml`, вы можете установить обработчик на событие ~`mailer.configure`~, и далее настроить почтовый объект в соответствии со своими требованиями. -You can connect to this event in your `ProjectConfiguration` class like shown below: +Это событие можно соединить в своём классе `ProjectConfiguration` следующим образом: [php] class ProjectConfiguration extends sfProjectConfiguration @@ -552,11 +547,11 @@ You can connect to this event in your `ProjectConfiguration` class like shown be } } -The following section illustrates a powerful usage of this technique. +Следующий раздел демонстрирует всю мощь использования этой техники. -### Using ~Swift Mailer Plugins~ +### Использование плагинов Swift Mailer -To use Swift Mailer plugins, listen to the `mailer.configure` event (see the section above): +Для использования плагинов Swift Mailer, установите обработчик события `mailer.configure` (см. раздел выше): [php] public function configureMailer(sfEvent $event) @@ -571,13 +566,13 @@ To use Swift Mailer plugins, listen to the `mailer.configure` event (see the sec } >**TIP** ->The ["Plugins"](http://swiftmailer.org/docs/plugins) section of the Swift Mailer official documentation describes all you need to know about the built-in plugins. +>Раздел ["Plugins (Плагины)"](http://swiftmailer.org/docs/plugins) официальной документации Swift Mailer описывает всё, что вам необходимо знать для использования встроенных плагинов. -### Customizing the Spool Behavior +### Настройка поведения очереди -The built-in implementation of the spools is very simple. Each spool retrieves all emails from the queue in a random order and sends them. +Встроенные реализации очереди отправляемых сообщений предельно просты. Они получают все сообщения в произвольном порядке и отправляют их. -You can configure a spool to limit the time spent to send emails (in seconds), or to limit the number of messages to send: +Вы можете настроить очередь отправляемых сообщений, ограничив затрачиваемое на отправку время, или ограничив общее число отправляемых сообщений: [php] $spool = $mailer->getSpool(); @@ -585,26 +580,26 @@ You can configure a spool to limit the time spent to send emails (in seconds), o $spool->setMessageLimit(10); $spool->setTimeLimit(10); -In this section, you will learn how to implement a priority system for the queue. -It will give you all the information needed to implement your own logic. +В этом разделе, вы научитесь реализовывать систему приоритетов для очереди. +Это даст вам необходимую информацию для реализации своей собственной логики. -First, add a `priority` column to the schema: +Во-первых, добавим к схеме столбец `priority`: [yml] - # for Propel + # для Propel mail_message: message: { type: blob, required: true } created_at: ~ priority: { type: integer, default: 3 } - # for Doctrine + # для Doctrine MailMessage: actAs: { Timestampable: ~ } columns: message: { type: blob, notnull: true } priority: { type: integer } -When sending an email, set the priority header (1 means highest): +Во-вторых, отправляя сообщение, установим заголовок приоритета (1 означает "важное"): [php] $message = $this->getMailer() @@ -613,10 +608,10 @@ When sending an email, set the priority header (1 means highest): ; $this->getMailer()->send($message); -Then, override the default `setMessage()` method to change the priority of the `MailMessage` object itself: +В-третьих, перепишем используемый по умолчанию метод `setMessage()` для изменения приоритета в самом объекте `MailMessage`: [php] - // for Propel + // для Propel class MailMessage extends BaseMailMessage { public function setMessage($message) @@ -628,7 +623,7 @@ Then, override the default `setMessage()` method to change the priority of the ` } } - // for Doctrine + // для Doctrine class MailMessage extends BaseMailMessage { public function setMessage($message) @@ -640,10 +635,11 @@ Then, override the default `setMessage()` method to change the priority of the ` } } -Notice that the message is serialized by the queue, so it has to be unserialized before getting the priority value. Now, create a method that orders the messages by priority: +Заметьте, сообщение сериализуется очередью, поэтому перед получением значения приоритета необходимо выполнить десериализацию. +Теперь, создадим метод, который упорядочит сообщения по приоритетам: [php] - // for Propel + // для Propel class MailMessagePeer extends BaseMailMessagePeer { static public function getSpooledMessages(Criteria $criteria) @@ -656,7 +652,7 @@ Notice that the message is serialized by the queue, so it has to be unserialized // ... } - // for Doctrine + // для Doctrine class MailMessageTable extends Doctrine_Table { public function getSpooledMessages() @@ -669,21 +665,21 @@ Notice that the message is serialized by the queue, so it has to be unserialized // ... } -The last step is to define the retrieval method in the `factories.yml` configuration to change the default way in which the messages are obtained from the queue: +Последним шагом станет определение метода получения сообщений в `factories.yml`, для изменения заданного по умолчанию пути извлечения сообщений из очереди: [yml] spool_arguments: [ MailMessage, message, getSpooledMessages ] -That's all there is to it. -Now, each time you run the `project:send-emails` task, each email will be sent according to its priority. +Вот и всё. +Теперь, при каждом запуске задачи `project:send-emails` сообщения будут посылаться в соответствии с их приоритетами. >**SIDEBAR** ->Customizing the Spool with any Criteria +>Настройка очереди сообщений с помощью любого критерия > ->The previous example uses a standard message header, the priority. ->But if you want to use any criteria, or if you don't want to alter the sent message, you can also store the criteria as a custom header, and remove it before sending the email. +>В предыдущем примере использовался стандартный заголовок сообщения - приоритет. +>Однако если вы хотите использовать любой критерий, или если вы не хотите изменять исходное сообщение, вы можете сохранять свой критерий как свой собственный заголовок, который перед отправкой будете удалять. > ->First, add a custom header to the message to be sent: +>Добавьте свой собственный заголовок к отправляемому сообщению: > > [php] > public function executeIndex() @@ -697,7 +693,7 @@ Now, each time you run the `project:send-emails` task, each email will be sent a > $this->getMailer()->send($message); > } > ->Then, retrieve the value from this header when storing the message in the queue, and remove it immediately: +>Затем при сортировке очереди получите значение этого заголовка и удалите его: > > [php] > public function setMessage($message) From e4373903687621d1413a23511c7b8a1645aadf7c Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:20:13 -0700 Subject: [PATCH 10/14] minor fix --- more-with-symfony/ru/04-Emails.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index b8e8cd5..3e91171 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -1,7 +1,7 @@ Электронная почта ================= -*by Fabien Potencier* +*автор Fabien Potencier; перевод на русский — BRIGADA* Благодаря использованию библиотеки [Swift Mailer](http://www.swiftmailer.org/), отправка электронных писем в symfony выполняется очень просто. Хотя всю работу делает ~Swift Mailer~, symfony выступает в роли "обёртки", которая позволяет сделать процесс отправки писем более гибким и мощным. From be83150974bb8371a4ea1d711744c59a1a9970a6 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:21:26 -0700 Subject: [PATCH 11/14] minor fix --- more-with-symfony/ru/03-Enhance-your-Productivity.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/03-Enhance-your-Productivity.markdown b/more-with-symfony/ru/03-Enhance-your-Productivity.markdown index 967c5a4..8254d9b 100644 --- a/more-with-symfony/ru/03-Enhance-your-Productivity.markdown +++ b/more-with-symfony/ru/03-Enhance-your-Productivity.markdown @@ -1,7 +1,7 @@ Повысьте свою продуктивность ============================ -*автор Fabien Potencier; перевод на русский — BRIGADA* +*автор: Fabien Potencier; перевод на русский — BRIGADA* Использование symfony само по себе уже повышает вашу продуктивность как вэб-разработчика. Конечно, все уже знают, как повышают продуктивность исключения symfony и отладочная панель. From 177b8fc45fe319bee07fbba5b8eac249bb5ebc91 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:23:11 -0700 Subject: [PATCH 12/14] minor fix --- more-with-symfony/ru/04-Emails.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/04-Emails.markdown b/more-with-symfony/ru/04-Emails.markdown index 3e91171..adb4007 100644 --- a/more-with-symfony/ru/04-Emails.markdown +++ b/more-with-symfony/ru/04-Emails.markdown @@ -1,7 +1,7 @@ Электронная почта ================= -*автор Fabien Potencier; перевод на русский — BRIGADA* +*автор: Fabien Potencier; перевод на русский — BRIGADA* Благодаря использованию библиотеки [Swift Mailer](http://www.swiftmailer.org/), отправка электронных писем в symfony выполняется очень просто. Хотя всю работу делает ~Swift Mailer~, symfony выступает в роли "обёртки", которая позволяет сделать процесс отправки писем более гибким и мощным. From 3b42dbe309669a77d3d5f8892e36e47c09cf3f80 Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:25:24 -0700 Subject: [PATCH 13/14] minor fix --- more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown b/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown index a96112f..5a44385 100644 --- a/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown +++ b/more-with-symfony/ru/05-Custom-Widgets-and-Validators.markdown @@ -10,7 +10,7 @@ ### Внутренности `sfWidgetForm` -Объект класса ~`sfWidgetForm`~ представляет визуальную реализацию того, как соответствующие данные должны редактироваться. Строковое значение, например, может быть изменено в простом текстовом поле, а может — в расширенном WYSIWYG-редакторе. Для обеспечения полноценного конфигурирования, класс `sfWidgetForm` имеет два важных свойства: `options` и `attributes`. +Объект класса ~`sfWidgetForm`~ представляет визуальную реализацию того, как соответствующие данные должны редактироваться. Например, строковое значение может быть изменено в простом текстовом поле, а может — в расширенном WYSIWYG-редакторе. Для обеспечения полноценного конфигурирования, класс `sfWidgetForm` имеет два важных свойства: `options` и `attributes`. * `options` — используется для конфигурирования виджета (например, задаётся запрос к базе данных для создания содержимого ниспадающего списка) From d27068fe44200f7dde320d5196990a1b1019f71a Mon Sep 17 00:00:00 2001 From: Mikhail Kotlyakov Date: Sun, 26 Sep 2010 04:26:25 -0700 Subject: [PATCH 14/14] minor fix --- more-with-symfony/ru/06-Advanced-Forms.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/more-with-symfony/ru/06-Advanced-Forms.markdown b/more-with-symfony/ru/06-Advanced-Forms.markdown index 40396b9..ba45b3c 100644 --- a/more-with-symfony/ru/06-Advanced-Forms.markdown +++ b/more-with-symfony/ru/06-Advanced-Forms.markdown @@ -1,7 +1,7 @@ Продвинутые формы ============== -*авторы: Ryan Weaver и Fabien Potencier; перевод на русский - BRIGADA* +*авторы: Ryan Weaver и Fabien Potencier; перевод на русский — BRIGADA* Среда обработки форм symfony снабжает разработчика инструментами, позволяющими легко выводить формы и проверять введённые в них данные. Благодаря имеющимся в каждой из ORM-систем классам ~`sfFormDoctrine`~ и ~`sfFormPropel`~, среда обработки форми позволяет легко выводить и сохранять формы, которые очень близки уровню данных.