Practical symfony 10 дахь өдөр: Формууд

Дурьдатгал:

Бидний орчуулгын хэсэгт маань Ганбат ах өөрийн хүчээ хавсаргалаа.  Таньд энэ хичээл таалагдсан бол Ганбат ахад баярлалаа гэж хэлэхээ мартав. Форм фраймворк бол symfony -ын нилээн тод давуу талын нэг. Формын талаар мэдэх нь та нэг том давааны цаана гарлаа л гэж мэд. За амжилт хүсье.

Орчуулсан Ш.Ганбат
(combat_mn@yahoo.com)

Practical symfony 10 дахь өдөр: Формууд

Өмнөх өдрийн хичээлийг бид Symfony дахь сорилтын фреймворкуудтай танилцах алхмаас гялалзтал эхлүүлсэн. Өнөөдөр бид формын фреймворкуудтай танилцах болно.

Формын фреймворк (The Form Framework)

Сайт бүр энгийн харилцааны формоос эхлэн олон тооны талбарууд бүхий нийлмэл түвэгтэй олон янзын формуудыг агуулсан байдаг. Форм бичнэ гэдэг нь формын HTML кодыг бичих, талбар нэг бүрийн утгыг шалгаж баталгаажуулах, өгөгдлийг боловсруулан баазад хадгалах, алдааны мэдээллүүдийг гаргах, түүнчлэн талбаруудыг дахин дахин бөглөж тэр бүрт формыг дуудах гээд вэб хөгжүүлэгчдийн хувьд өдөр тутам тохиолддог хялбар биш уйтгартай ажлуудын нэг билээ.

Дугуйг дахин дахин эргүүлэх нь мэдээж утгагүй хэрэг. Ийм учраас Symfony нь формын удирдлагыг хялбаршуулах зорилгоор тусгайлсан фреймворкийг бий болгосон байна. Формын фреймворк нь гурван хэсгээс бүрдэнэ:

  • validation: Валидэйтор – энэ бол оролтын өгөгдлийг (integer, string, email хаяг, …) шалгах зориулалт бүхий классуудыг агуулсан дэд фреймворк
  • widgets: Виджетүүд энэ бол формын HTML талбаруудыг (input, textarea, select, …)  харуулах классууд бүхий дэд фреймворк
  • forms: Формууд энэ бол өөртөө виджетүүд болон валидэйторуудыг нэгтгэсэн, формыг удирдахад туслах тохиромжтой арга хэрэгсэл бүхий классууд. Форм дахь талбар бүр нь өөр өөрийн валидэйтор болон виджеттэй байдаг.

Формууд

Symfony-н формууд нь талбаруудаас бүрдэх класс юм. Талбар бүр нь нэр, валидэйтор, виджеттэй. Энгийн харилцах форм (ContactForm)-ыг дараах классаар илэрхийлж болно:

class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'email'   => new sfWidgetFormInputText(),
      'message' => new sfWidgetFormTextarea(),
    ));

    $this->setValidators(array(
      'email'   => new sfValidatorEmail(),
      'message' => new sfValidatorString(array('max_length' => 255)),
    ));
  }
}

Формын талбарууд нь setValidators() ба setWidgets() методуудын тусламжтайгаар    configure()методод байршдаг.

Формын фреймворк нь олон тооны виджет болон валидэйтортой холбогддог. API-д тэдгээрийн бүгдийнх нь сонголтууд, алдаанууд, алдааны дефаолт мессежүүдийн талаар дэлгэрэнгүй тусгасан.

Валидэйтор, виджетүүдийн нэрс нь маш илэрхий, тухайлбал: email талбар нь (sfWidgetFormInput) HTML-рүү <input> тэг болж хөрвөх бөгөөд , и-мэйл хаяг (sfValidatorEmail) гэсэн нөхцлөөр,  message талбар нь (sfWidgetFormTextarea) <textarea> тэг болох бөгөөд 255 тэмдэгтээс илүүгүй урттай string (sfValidatorString) гэсэн нөхцлөөр тус тус шалгагдана.

Бүх талбаруудын required сонголтын дефаолт утга нь true байдаг тул email-ийн валидэйтор нь  new sfValidatorEmail(array('required' => true))гэсэнтэй адил юм.

Та mergeForm() методын тусламжтайгаар, эсвэл embedForm()метод ашиглан нэг формыг нь нөгөөд нь байрлуулах замаар хоёр формыг нэгтгэх боломжтой:

$this->mergeForm(new AnotherForm());
$this->embedForm('name', new AnotherForm());

Формын Doctrine

Формуудын мэдээлээл нь тогтмол хугацаанд серилэгдэн (serialized) баазад хадгадагдаж байдаг. Symfony таны моделийн талаар бүгдийг мэдэж байдаг бөгөөд, дээрхи мэдээлэл дээр суурилан формуудыг автоматаар үүсгэдэг. Жишээлбэл, 3 дахь өдөр та doctrine:build --all таскийг дуудахад Symfony нь doctrine:build --forms таскийг автоматаар дуудсан гэсэн үг:

$ php symfony doctrine:build --forms

doctrine:build --forms таск нь формын классуудыг үүсгэн lib/form/ директорит хадгалдаг. Үүссэн файлуудын зохион байгуулалт нь lib/model/-ийнхтэй ойролцоо. Моделийн класс бүр нь формын класстай холбоотой (ж.нь JobeetJob ба JobeetJobForm) бөгөөд, суурь классаа өвлөдөг тул анхны хэлбэртээ ямар нэг метод агуулдаггүй, хоосон байдаг:

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
  }
}

lib/form/doctrine/base/ дэд директори дахь файлуудаас Та Symfony-н виджет ба валидэйторуудын олон сайхан жишээг олж үзэх боломжтой.

Та тодорхой моделийн хувьд харгалзах параметрүүдийг symfony-н Doctrine-д зааж өгснөөр форм үүсгэлтийг хориглох боломжтой:

SomeModel:
  options:
    symfony:
      form: false
      filter: false

Ажлын байрны формыг тохируулах (Customizing the Job Form)

Ажлын байрны форм нь формуудын ажиллагааг судлах сайхан жишээ юм. Энэхүү тохиргоог алхам алхмаар харцгаая.

Эхлээд layout дахь “Post a job&#8221; линкийг бүх өөрчлөлтүүд таны браузерт шууд харагддаг байхаар болгон өөрчилье:

<!-- apps/frontend/templates/layout.php -->
<a href="<?php echo url_for('@job_new') ?>">Post a Job</a>

Формын doctrine нь дефаолтдаа хүснэгтийн бүх багануудын талбарыг харуулдаг. Гэв ч ажлын байрны формын хувьд тэдгээрийн зарим хэсгийг эцсийн хэрэглэгч засварлах боломжгүй байх ёстой. Ийм талбаруудыг unset() методоор формоос хасч болно.

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['is_activated']
    );
  }
}

Талбаруудыг формоос хасахад холбогдох виджет ба валидэйтор нь дагаж устдаг.

Мөн та харуулахыг хүсэхгүй байгаа талбаруудаа формоос хасахын оронд, формд харуулах талбаруудыг useFields() метод ашиглан тодорхой зааж өгч болно:

// lib/form/doctrine/JobeetJobForm.class.php
        class JobeetJobForm extends BaseJobeetJobForm
    {
      public function configure()
      {
        $this->useFields(array('category_id', 'type', 'company', 'logo', 'url', 'position', 'location', 'description', 'how_to_apply', 'token', 'is_public', 'email'));
      }
    }

useFields() метод нь нууц талбарууд нэмэх болон талбаруудын дарааллыг өөрчлөх массив гэсэн хоёр үйлдлийг автоматаар танд зориулж хийдэг.

Талбаруудыг тодорхой зааж өгсөн нөхцөлд суурь формд нэмсэн шинэ талбар таны формд автоматаар гарч ирэхгүй (өгөгдлийн сангийн холбогдох хүснэгтэд шинэ багана нэмсэн моделийн формыг сана).

Зарим тохиолдолд формыг өгөгдлийн баазын схемийн шинж чанарыг шууд өвлөж авснаас илүү нарийвчлалтайгаар тохируулах шаардлага гардаг. Жишээлбэл, email багана нь баазын схемдээ varchar, харин бидэнд түүнийг и-мэйл эсэхийг шалгах шаардлагатай. Тэгэхээр дефаолтаар өгөгдсөн sfValidatorString-ийг sfValidatorEmail болгон өөрчилье:

// lib/form/doctrine/JobeetJobForm.class.php
public function configure()
{
  // ...

  $this->validatorSchema['email'] = new sfValidatorEmail();
}

Дефаолт валидэйторыг солих нь дандаа шилдэг шийдэл байдаггүй. Учир нь энэ ажиллагааны улмаас өгөгдлийн баазын схемээс уламжилсан баталгаажуулах зарчим нь алдагддаг (new sfValidatorString(array('max_length' => 255))). Иймд бараг бүх тохиолдолд одоо ашиглагдаж байгаа дээрээ нэмж шинэ валидэйтор sfValidatorAnd тавьж өгөх нь зохимжтой байдаг.

// lib/form/doctrine/JobeetJobForm.class.php
public function configure()
{
  // ...

  $this->validatorSchema['email'] = new sfValidatorAnd(array(
    $this->validatorSchema['email'],
    new sfValidatorEmail(),
  ));
}

sfValidatorAnd валидэйтор нь өгөгдлийг шалгах валидэйторуудын массивыг өөрийн утгандаа авдаг. Үүний арга заль нь бид одоо хэрэглэгдэж буй валидэйтор руу өгөгдлийг илгээхийн хажуугаар бас нэг шинийг нэмж байгаад оршино.

Түүнчлэн та sfValidatorOr валидэйторыг ашиглаж болно. Энэ тохиолдолд ядаж аль нэг валидэйторт тэнцсэн нөхцөлд түүний утгыг зөв гэж тооцдог. Иймд мэдээжээр та нарийн логиктой валидэйтор бий болгоё гэвэл sfValidatorAnd ба sfValidatorOr валидэйторуудыг нэгтгэн ашиглаж болно.

type багана нь баазын схемд varchar хэдий ч, бид түүний утгыг урьчилан тодорхойлсон full time, part time юм уу freelance жагсаалтаар хязгаарлахыг хүсэж байна.

Үүний тулд эхлээд JobeetJobTable дахь боломжит утгуудыг тодорхойлцгооё:

// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  static public $types = array(
    'full-time' => 'Full time',
    'part-time' => 'Part time',
    'freelance' => 'Freelance',
  );

  public function getTypes()
  {
    return self::$types;
  }

  // ...
}

Үүний дараа type талбарт sfWidgetFormChoice виджетийг хэрэглэцгээе:

$this->widgetSchema['type'] = new sfWidgetFormChoice(array(
  'choices'  => Doctrine_Core::getTable('JobeetJob')->getTypes(),
  'expanded' => true,
));

sfWidgetFormChoice виджет нь сонголтын хэрэгсэл бөгөөд, зарим тохиргооноосоо хамаарч (expanded ба multiple) бусад виджетүүдэд янз бүрээр илэрхийлэгдэж болно:

  • Dropdown list (<select>): array('multiple' => false, 'expanded' => false)
  • Dropdown box (<select multiple="multiple">): array('multiple' => true, 'expanded' => false)
  • List of radio buttons: array('multiple' => false, 'expanded' => true)
  • List of checkboxes: array('multiple' => true, 'expanded' => true)

Хэрэв та radio button-уудын аль  нэгийг дефаолтаар сонгуулахыг хүсэж байвал (ж.нь full-time) баазын схем дахь дефаолт утгыг өөрчилж болно.

Хэдийгээр та одоо ямар ч хүн буруу өгөгдөл оруулж чадахгүй гэж бодож байгаа ч хакер curl юмуу Firefox Web Developer Toolbar ашиглан виджетийг хялбархнаар тойрон гарч чадна. Иймд валтдэйторыг сольж сонголтуудыг хязгаарлая:

$this->validatorSchema['type'] = new sfValidatorChoice(array(
  'choices' => array_keys(Doctrine_Core::getTable('JobeetJob')->getTypes()),
));

logo талбарт тухайн ажлын байранд хамаарах байгууллагын логоны файлын нэр хадгалагдах бөгөөд бид виджетийг file input тэг болгон өөрчлөх шаардлагатай.

$this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
  'label' => 'Company logo',
));

Symfony-н форм нь талбар бүрт label-ийг автоматаар үүсгэдэг (<label> тэг) бөгөөд түүнийг setLabels() метод ашиглан өөрчилж болно:

$this->widgetSchema->setLabels(array(
  'category_id'    => 'Category',
  'is_public'      => 'Public?',
  'how_to_apply'   => 'How to apply?',
));

Бид  бас дефаолт валидэйторыг өөрчлөх шаардлагатай:

$this->validatorSchema['logo'] = new sfValidatorFile(array(
  'required'   => false,
  'path'       => sfConfig::get('sf_upload_dir').'/jobs',
  'mime_types' => 'web_images',
));

sfValidatorFile нь нэлээд сонирхол төрүүлэхүйц бөгөөд, олон зүйлийг хийдэг:

  • Тухайн файл вэбд нийцэх формат (mime_types) мөн эсэхийг шалгана
  • Файлын нэрийг давхцуулалгүйгээр оноож өгнө
  • Файлыг зааж өгсөн замаар (given path) хадгадна
  • logo талбарыг үүссэн нэрээр нь шинэчилнэ

Та логонуудыг хадгалах шинэ директори (web/uploads/jobs/) үүсгээд, бичилт хийх боломжтой эсэхийг нягтла.

Валидэйтор нь баазад зөвхөн файлын нэрийг хадгалдаг тул showSuccess темплэйтэд хэрэглэгдэж буй логоны замыг өөрчил.

// apps/frontend/modules/job/templates/showSuccess.php
<img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" />

Хэрэв модольд generateLogoFilename() метод тодорхойлогдсон байвал тэр нь валидэйторт дуудагдаж, үүний дүнд логоны файлын дефаолтаар үүссэн нэр өөрчлөгддөг. Метод нь аргументдаа sfValidatedFile объектыг авдаг.

Дээрхийн адилаар дурын label-ийг өөрчлөх боломжтой тул үүнийг ашиглан та формын талбарууддад тайлбар (help) хийж өгч болно:

$this->widgetSchema->setHelp('is_public', 'Whether the job can also be published on affiliate websites or not.');

Эцэст нь JobeetJobForm класс дараах байлалтай болно:

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['is_activated']
    );

    $this->validatorSchema['email'] = new sfValidatorAnd(array(
      $this->validatorSchema['email'],
      new sfValidatorEmail(),
    ));

    $this->widgetSchema['type'] = new sfWidgetFormChoice(array(
      'choices'  => Doctrine_Core::getTable('JobeetJob')->getTypes(),
      'expanded' => true,
    ));
    $this->validatorSchema['type'] = new sfValidatorChoice(array(
      'choices' => array_keys(Doctrine_Core::getTable('JobeetJob')->getTypes()),
    ));

    $this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
      'label' => 'Company logo',
    ));

    $this->widgetSchema->setLabels(array(
      'category_id'    => 'Category',
      'is_public'      => 'Public?',
      'how_to_apply'   => 'How to apply?',
    ));

    $this->validatorSchema['logo'] = new sfValidatorFile(array(
      'required'   => false,
      'path'       => sfConfig::get('sf_upload_dir').'/jobs',
      'mime_types' => 'web_images',
    ));

    $this->widgetSchema->setHelp('is_public', 'Whether the job can also be published on affiliate websites or not.');
  }
}

Формын темплэйт

Формын классын тохиргоо нэгэнт хийгдсэн болохоор одоо бид түүнийг дэлгэц дээр гаргая. Job формын темплэйт нь танд шинэ ажлын байр үүсгэхэд, эсвэл ажлын байрны мэдээллийг засварлахад хэрэг болно. Нэмж хэлэхэд newSuccess.php болон editSuccess.php темплэйтүүд хоорондоо маш төстэй:

<!-- apps/frontend/modules/job/templates/newSuccess.php -->
<?php use_stylesheet('job.css') ?>

<h1>Post a Job</h1>

<?php include_partial('form', array('form' => $form)) ?>

Хэрэв та job–ын CSS-ийг оруулж амжаагүй байгаа бол яг одоо түүнийг хоёр темплэйтдээ оруулж өгөх (<?php use_stylesheet('job.css') ?>) цаг нь болсон.

Форм нь өөрөө хэсэгчилсэн (partial) _form болж үүсдэг. _form-ын кодыг дараах кодоор соль:

<!-- apps/frontend/modules/job/templates/_form.php -->
<?php use_stylesheets_for_form($form) ?>
<?php use_javascripts_for_form($form) ?>

<?php echo form_tag_for($form, '@job') ?>
  <table id="job_form">
    <tfoot>
      <tr>
        <td colspan="2">
          <input type="submit" value="Preview your job" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form ?>
    </tbody>
  </table>
</form>

use_javascripts_for_form() болон use_stylesheets_for_form() хэлперүүд нь формын виджетэд шаардагдах JavaScript болон CSS файлуудыг холбож өгдөг.

Ажлын байрны формуудад JavaScript, CSS хэрэггүй байсан ч тэдгээрийг “юмыг яаж мэдэх вэ” гээд үлдээхэд муу зүйл байхгүй. Энэ нь та хэрэв хэзээ нэгэн цагт формыг өөрчлөхөөр шийдэж, JavaScript юмуу CSS нэмэхээр бол цагийг тань хэмнэх ач тустай.

form_tag_for()хэлпер нь тухайн формын <form> тэгийг үүсгэдэг ба объект нь шинэ үү үгүй юу гэдгээс үл хамааран HTTP методыг POST эсвэл PUT хийх зорилгоор өөрчлөх юмуу route хийдэг. Түүнчлэн тухайн форм ядаж нэг ширхэг file input тэг агуулж байвал хэлпер нь multipart атрибютад санаа тавьдаг.

Эцэст нь формын виджетийг <?php echo $form ?> үүсгэнэ.

Формын гадаад төрх ба мэдрэмж (feel)-ийг тохируулах

<?php echo $form ?> нь дефаолтдаа формын виджетийг хүснэгт хэлбэрээр үүсгэдэг.

Формын гадаад төрхийг өөрчлөх шаардлага танд цөөн биш тохиолдоно. Форм объект нь үүнд зориулсан олон методыг танд санал болгодог:

Метод Тайлбар
render() Формыг үүсгэнэ (echo $form-той ижил)
renderHiddenFields() Нууц талбар үүсгэнэ (hidden fields)
hasErrors() Формд алдаа байвал true утгыг буцаана
hasGlobalErrors() Формд глобал алдаа байвал true утгыг буцаана
getGlobalErrors() Глобал алдаануудыг массив хэлбэрээр буцаана
renderGlobalErrors() Глобал алдаануудыг харуулна

Формтой ажиллахдаа түүнийг талбаруудын (field) массив гэж ойлгож болно. Тухайлбал та company талбар руу $form['company'] гэж хандаж болно. Буцаан илгээгдэх объект нь энэхүү талбарын элемент нэг бүрийг илэрхийлэх методуудыг тодорхойлсон байна:

Метод Тайлбар
renderRow() Талбарын мөрийг үүсгэнэ
render() Талбарын виджетийг үүсгэнэ
renderLabel() Талбарын label тэгийг үүсгэнэ
renderError() Хэрэв алдаа байвал түүнийг харуулна
renderHelp() Талбарт зорулсан тусламжийн мэдээллийг харуулна

echo $form нь дараахтай ижил:

<?php foreach ($form as $widget): ?>
  <?php echo $widget->renderRow() ?>
<?php endforeach ?>

Формын үйлдлүүд

Бидэнд одоо формын класс болон түүнийг харуулах темплэйт байна. Тэгэхээр энэ бүхэн ажиллаж ямар нэг үйлдэл хийх цаг нь болсон гэсэн үг.

Ажлын байрны форм нь job модулийн таван үйлдлийг удирддаг:

  • new: Ажлын байр үүсгэх хоосон формыг харуулна
  • edit: Ажлын байрыг засварлах формыг харуулна
  • create: Хэрэглэгчийн оруулсан өгөгдөл бүхий шинэ ажлын байрыг үүсгэнэ
  • update: Хэрэглэгчийн өөрчилсөн өгөгдлийн дагуу ажлын байрны мэдээллийг шинэчилнэ
  • processForm: create ба update үйлдлүүдээр дуудагддаг (баталгаажуулах, талбар бөглөлтийг хянах, формыг серилж баазад хадгалах үүрэгтэй)

Бүх форм дараах амьдралын мөчлөгийг туулдаг:

Бид 5 хоногийн өмнө job модулийн Doctrine-ий route-үүдийг үүсгэсэн. Одоо тэдгээрийг формын үйлдлүүдийг удидахад зориулан хялбаршуулъя:

// apps/frontend/modules/job/actions/actions.class.php
public function executeNew(sfWebRequest $request)
{
  $this->form = new JobeetJobForm();
}

public function executeCreate(sfWebRequest $request)
{
  $this->form = new JobeetJobForm();
  $this->processForm($request, $this->form);
  $this->setTemplate('new');
}

public function executeEdit(sfWebRequest $request)
{
  $this->form = new JobeetJobForm($this->getRoute()->getObject());
}

public function executeUpdate(sfWebRequest $request)
{
  $this->form = new JobeetJobForm($this->getRoute()->getObject());
  $this->processForm($request, $this->form);
  $this->setTemplate('edit');
}

public function executeDelete(sfWebRequest $request)
{
  $request->checkCSRFProtection();

  $job = $this->getRoute()->getObject();
  $job->delete();

  $this->redirect('job/index');
}

protected function processForm(sfWebRequest $request, sfForm $form)
{
  $form->bind(
    $request->getParameter($form->getName()),
    $request->getFiles($form->getName())
  );

  if ($form->isValid())
  {
    $job = $form->save();

    $this->redirect('job_show', $job);
  }
}

Та /job/new хуудсыг нээвэл шинэ форм үүсч темплэйт рүү өгөгдөнө (new үйлдэл).

Хэрэглэгч формыг илгээхэд (create үйлдэл) форм нь өгөгдлүүдтэй холбогдож (bind() метод) валидэйторыг дууддаг.

Форм нь холбогдомогцоо өгөгдөл зөв эсэхийг isValid()методын тусламжтайгаар шалгана: Хэрэв формын өгөдөл зөв бол (returns true) тухайн ажлын байр баазад хадгалагдаж ($form->save()) хэрэглэгчийн өмнө сая үүссэн ажлын байрыг харах хуудас нээгдэнэ; Хэрэв өгөгдөл буруу бол newSuccess.php темплэйт алдааны мессежүүдийн хамт дахин дуудагдана.

setTemplate() метод нь тухайн үйлдэлд ашиглагдсан темплэйтийг өөрчилдөг. Хэрэв формын өгөгдөл буруу бол  create ба update үйлдлүүд нь тухайн формыг алдааны мессежүүдийн хамт дахин харуулахдаа new болон edit үйлдлүүдэд ашигласан темплэйтийг хэрэглэнэ.

Ажлын байрны мэдээллийг засварлах ажиллагаа нь дээрхтэй бараг ижил. New ба edit үйлдлүүдийн хоорондын ганц ялгаа нь формын конструкторт эхний аргументаар засварлагдаж буй Job объект илгээгддэг.

Энэ объект нь темплэйт дахь виджетийн дефаолт утгад хэрэглэгдэнэ (дефаолт утга нь формын Doctrine-д объект хэлбэртэй, ердийн формуудын хувьд энгийн массив хэлбэртэй байна).

Түүнчлэн та анх форм үүсгэхдээ л түүний дефаолт утгыг тодорхойлж өгч болно. Үүнийг нэг бол баазын схемд утгыг шууд зарлах, эсвэл урьчилан засварласан Job объектыг формын конструкторт илгээх аргуудын аль нэгээр хэрэгжүүлэх боломжтой.

executeNew() методыг өөрчилж,  type баганын дефаолт утгыг full-time болгоё:

// apps/frontend/modules/job/actions/actions.class.php
public function executeNew(sfWebRequest $request)
{
  $job = new JobeetJob();
  $job->setType('full-time');

  $this->form = new JobeetJobForm($job);
}

Форм холбогдсон үед түүний дефаолт утга нь хэрэглэгчийн оруулсан утгаар солигддог. Энэхүү өгөгдөл нь алдаа гарсан нөхцөлд формыг дахин бөглөхөд хэрэглэгдэнэ.

Job формыг токеноор хамгаалах

Ингээд л бүх зүйл сайхан ажиллана. Одоо хэрэглэгч ажлын байранд өөрийн токен (өвөрмөц тэмдэглэгээ)-ийг оруулна. Гэхдээ бид хэрэглэгчийн оруулах токен цорын ганц байж чадах эсэхэд эргэлзэж байгаа учраас түүнийг ажлын байрыг анх оруулахдаа автоматаар үүсгэх нь зохимжтой.

JobeetJob класс дахь save() методыг баяжуулж, ажлын байрыг хадгалахын өмнө токенийг үүсгэдэг болгоё:

// lib/model/doctrine/JobeetJob.class.php
public function save(Doctrine_Connection $conn = null)
{
  // ...

  if (!$this->getToken())
  {
    $this->setToken(sha1($this->getEmail().rand(11111, 99999)));
  }

  return parent::save($conn);
}

Одоо та формоос токений талбарыг хасч болно:

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['is_activated'],
      $this['token']
    );

    // ...
  }

  // ...
}

Магадгүй та 2 дахь өдрийн хичээлээс ажлын байрны мэдээллийг хэрэглэгч зөвхөн түүний токенийг мэдэж буй тохиолдолд засварлах боломжтой гэсэн байсныг санаж байгаа биз. Гэтэл одоо бол түүний URL-ийг таахад л засварлах боломжтой. Яагаад гэвэл засварлах URL нь job/ID/edit гэсэн хэлбэртэй, ингэхдээ ID нь ажлын байрны мэдээллийн анхдагч түлхүүр.

sfDoctrineRouteCollection route нь дефаолтаараа анхдагч түлхүүр бүхий URL-ийг үүсгэдэг ч түүнийг бид column хэмжигдэхүүнд дурын цор ганц (unique) талбарыг зааж өгөх замаар өөрчилж болно.

# apps/frontend/config/~routing|Routing~.yml
job:
  class:        sfDoctrineRouteCollection
  options:      { model: JobeetJob, column: token }
  requirements: { token: \w+ }

Symfony-д цор ганц түлхүүрийг \d+ гэж томъёолдог байтал token параметрт үүнийг \w+ гэсэн байгааг анзаарна уу.

Одоо ажлын байрны job_show_user-ээс бусад бүх route токентой болсон. Жишээлбэл, ажлын байрны мэдээлэл засварлах route нь дараах загварт тохирно:

http://www.jobeet.com.localhost/job/TOKEN/edit

Харин одоо бидэнд showSuccess темплэйт дахь “Edit&#8221; линкийг өөрчлөх шаардлагатай:

<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<a href="<?php echo url_for('job_edit', $job) ?>">Edit</a>

Урьдчилан харах хуудас

Урьдчилан харах хуудас – энэ бол ажлын байрны мэдээллийг дэлгэц дээр харуулах хуудас юм. Хэрэв хэрэглэгч зөв токен оруулсан бол тэр нь route-ийн тусламжтайгаар URL-д бичигдэнэ.

Токен бүхий URL байгаа нөхцөлд бид дээд хэсэгт нь админ-баарыг нэмж өгнө. showSuccess темплэйтийн эхэн хэсэгт админ-баарын кодыг нэмж, доод хэсэгт нь байгаа edit линкийг устгая:

<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<?php if ($sf_request->getParameter('token') == $job->getToken()): ?>
  <?php include_partial('job/admin', array('job' => $job)) ?>
<?php endif ?>

Дараа нь _admin partial-ийг үүсгэе:

<!-- apps/frontend/modules/job/templates/_admin.php -->
<div id="job_actions">
  <h3>Admin</h3>
  <ul>
    <?php if (!$job->getIsActivated()): ?>
      <li><?php echo link_to('Edit', 'job_edit', $job) ?></li>
      <li><?php echo link_to('Publish', 'job_edit', $job) ?></li>
    <?php endif ?>
    <li><?php echo link_to('Delete', 'job_delete', $job, array('method' => 'delete', 'confirm' => 'Are you sure?')) ?></li>
    <?php if ($job->getIsActivated()): ?>
      <li<?php $job->expiresSoon() and print '' ?>>
        <?php if ($job->isExpired()): ?>
          Expired
        <?php else: ?>
          Expires in <strong><?php echo $job->getDaysBeforeExpires() ?></strong> days
        <?php endif ?>

        <?php if ($job->expiresSoon()): ?>
         - <a href="">Extend</a> for another <?php echo sfConfig::get('app_active_days') ?> days
        <?php endif ?>
      </li>
    <?php else: ?>
      <li>
        [Bookmark this <?php echo link_to('URL', 'job_show', $job, true) ?> to manage this job in the future.]
      </li>
    <?php endif ?>
  </ul>
</div>

Нэлээд их код бичигдсэн ч дийлэнх нь энгийн, ойлгомжтой байгаа биз.

Темплэйтийг уншихад илүү хялбар болгох үүднээс бид JobeetJob классд шорткат методуудын холбоосыг (bunch of shortcut methods) нэмж орууллаа.

// lib/model/doctrine/JobeetJob.class.php
public function getTypeName()
{
  $types = Doctrine_Core::getTable('JobeetJob')->getTypes();
  return $this->getType() ? $types[$this->getType()] : '';
}

public function isExpired()
{
  return $this->getDaysBeforeExpires() < 0;
}

public function expiresSoon()
{
  return $this->getDaysBeforeExpires() < 5;
}

public function getDaysBeforeExpires()
{
  return ceil(($this->getDateTimeObject('expires_at')->format('U') - time()) / 86400);
}

Админ-баар нь ажлын байрны статусаас хамаарч харгалзах өөр өөр үйлдлийг харуулдаг.

Дараагийн бүлэгт та “activated&#8221; баарыг харах болно.

Ажлын байрыг идэвхжүүлэх ба нийтлэх

Өмнөх бүлэгт бид Publish линкийг үүсгэсэн. Энэ линк нь publish үйлдлийг шинээр дуудах ёстой. Үүнд зориулж шинэ route үүсгэхийн оронд бид одоо байгаа job route-дээ нэмэлт тохиргоо хийе:

# apps/frontend/config/routing.yml
job:
  class:   sfDoctrineRouteCollection
  options:
    model:          JobeetJob
    column:         token
    object_actions: { publish: put }
  requirements:
    token: \w+

object_actions нь тухайн объектийн нэмэлт үйлдлүүдэд зориулсан массив юм. Одоо бид “Publish&#8221; линкийг өөрчилж болно:

<!-- apps/frontend/modules/job/templates/_admin.php -->
<li>
  <?php echo link_to('Publish', 'job_publish', $job, array('method' => 'put')) ?>
</li>

Сүүлийн алхам нь publish үйлдлийг үүсгэх:

// apps/frontend/modules/job/actions/actions.class.php
public function executePublish(sfWebRequest $request)
{
  $request->checkCSRFProtection();

  $job = $this->getRoute()->getObject();
  $job->publish();

  $this->getUser()->setFlash('notice', sprintf('Your job is now online for %s days.', sfConfig::get('app_active_days')));

  $this->redirect('job_show_user', $job);
}

Анхааралтай уншигч “Publish&#8221; линк нь HTTP-н put методоор илгээгдэж байгааг анзаарах боломжтой. Put методыг дууриах (симуляци)-ын тулд  линк нь товчлуур дарагдмагц автоматаар форм болон хөрвөдөг.

Бидэнд CSRF хамгаалалт идэвхжсэн байгаа тул link_to() хэлпер нь CSRF токенийг линкдээ оруулснаар request-ийн checkCSRFProtection() метод нь тухайн өгөгдөл зөв эсэхийг шалгадаг.

executePublish() метод нь publish()гэсэн шинэ методыг ашигладаг ба үүнийг дараах байдлаар тодорхойлж болно:

// lib/model/doctrine/JobeetJob.class.php
public function publish()
{
  $this->setIsActivated(true);
  $this->save();
}

Одоо та браузер дээрээ шинээр ажлын байр нийтэлж туршиж болно.

Гэв ч бидэнд сайжруулах зүйл бас байна. Идэвхгүй болсон ажлын байрууд ямар ч нөхцөлд харагдах ёсгүй. Өөрөөр хэлбэл тэд Jobeet-ийн нүүр хуудсанд гарч ирэхгүй байхын зэрэгцээ холбогдох URL-ээр нээгдэхгүй байх ёстой. Бидэнд addActiveJobsQuery()метод байгаа болохоор Doctrine_Query-г зөвхөн идэвхтэй ажлын байруудыг харуулдаг байхаар болгож хязгаарлалт хийх боломжтой. Үүний тулд түүнд зөвхөн шинэ нөхцөл нэмэхэд л хангалттай:

// lib/model/doctrine/JobeetJobTable.class.php
public function addActiveJobsQuery(Doctrine_Query $q = null)
{
  // ...

  $q->andWhere($alias . '.is_activated = ?', 1);

  return $q;
}

Ингээд л боллоо. Одоо энэ бүхнийг браузерт туршиж болно. Идэвхгүй болсон бүх ажлын байрууд нүүр хуудаснаас хасагдсан; тэр бүү хэл шууд хандах URL-ийг нь мэдэж байсан ч та тэдгээрийг харж чадахгүй. Зөвхөн та токенийг нь мэдэж байгаа нөхцөлд л тэдгээр мэдээлийг харах боломжтой бөгөөд энэ тохиолдолд ажлын байрны мэдээлэл нь админ-баартайгаар харагдана.

Энэ бол бид өнгөрсөн хугацааны туршид бий болгосон MVC pattern болон refactorization-ий олон давуу талуудын нэг. Шинэ нөхцөл нэмэхийн тулд ердөө ганц методод ганцхан өөрчлөлт хийх л шаардлагатай байлаа.

Бид getWithJobs() методыг үүсгэхдээ addActiveJobsQuery() методыг хэрэглэхээ мартсан. Үүнийгээ засварлаад шинэ нөхцөл нэмье:

class JobeetCategoryTable extends Doctrine_Table
{
  public function getWithJobs()
  {
    // ...

    $q->andWhere('j.is_activated = ?', 1);

    return $q->execute();
  }

Маргааш уулзацгаая

Өнөөдрийн хичээлд маш их шинэ мэдээлэл байлаа. Гэвч эдгээр нь Symfony-н формтой ажиллахад маш чухал ач холбогдолтой.

Та нарын зарим нь өнөөдөр бидний мартсан зүйлийг анзаарсан гэдгийг би мэдэж байна…  Бид шинэ features-дээ ямар ч сорилт хийгээгүй. Апликейшн хөгжүүлэхэд хамгийн чухал зүйл нь сорилт бичих явдал учраас энэ бүхнийг бид маргааш эхний ээлжинд хийх болно.

« 9 дэх өдөр: Функциональ сорилт 11 дэх өдөр: Формын сорилт »

 

Татаж авах холбоос: http://hotfile.com/dl/81859491/92f75d1/PracticalsymfonyDay10.docx.html

Published by

Think!

Web developer. Open source enthusiast.

9 thoughts on “Practical symfony 10 дахь өдөр: Формууд”

  1. Jobeet -ийг анхлан сурч байгаа хүмүүст орчуулан хүргэж байгаа нь тун сайшаалтай сайн ажил болжээ. Харин оруулга дээрээ зарим үг хэллэгийг анхаарвал зүгээр санагдана. Жишээ нь дефаолт утга гэдгийг анхны утга энэ тэр гэвэл илүү болох юм болуу гэж бодож байна. Амжилт хүсье.

    1. Бүх үгийг монголчлох биш хамгийн гол нь уншиж байгаа хүнд ойлгомжтойгоор хүргэхийг л бодож байгаа. Дараа дараагийнхаа орчуулгад анхаарнаа.

  2. Buh ugiig mongoloor orchuulj helj ugaasaa odoogoor bolohgyi gol ni utgachilj oruulah heregtei. ta nariin oruulga yag unendee bol mash modon oruulga bolson baina. yavaandaa turshlagajina gej naidaj bna.Orchuulsan humuus ni web programming symfony-oo sain medehgyi bol bas yavahgyi l dee. orchuulga ni

    1. Ямартай ч саналаа бичсэнд баярлалаа. Мэдээж алдаа байлгүй яахав гэхдээ өөрт чинь тэгээд модон бишээр орчуулсан ямар нэгэн юм байгаамуу. Хэн болчоод юу заах гээд байнаа ?

  3. Sain baina uu neg asuudal baina bi projectoo shared host deer tawihdaa web hawtsiig public_html dotor huulj tawiad ajillaj baigaan getel zurag upload hiisen chin sf_projects/minii_project hawtsand shineer web/uploads gej uuseed zurag orchood baihiin public_html dotorh uploads dotor zurag huulagdahgui baina ene asuudliig shiidhed tuslaach humuusee
    Thanks

    1. web дотрохоо public_html дотор хийгээд бусдийг нь өөр хавтас үүсгэж хийгээд тэгээд application-нийхаа тохиргоог тэр хавтас руугаа зааж өгнө.

      Бүүр болохгүй бол доорх линк-ээс хар.
      http://trac.symfony-project.org/wiki/InstallingSymfonyOnSharedHostNoSsh

      http://www.symfony-project.org/book/1_0/19-Mastering-Symfony-s-Configuration-Files

  4. Шинээр job нэмэхэд зураг оруулахаар л удаад алга болчоод нэмэгдэхгүй юм. Зураг оруулаагүй бол зүгээр нэмэгдээд байх юм юунийх вэ хэлж өгч туслаач

    1. Илгээгдэж байгаа файлаа POST хийгдэж байгаа дээр нь барьж аваад байгаа үгүйг нь шалга. Эсвэл PHP-ын анхдагч утга болох файлын хэмжээнээс нь том юмуу, уншигдаг хугацаа нь хэт бага байвал тэгээд юу ч гарахгүй цагаардаг санагдана. Файлынхаа maxsize-ыг нь Validator дээрээ мөн заагаад өгчиж болно бас.

      За амжилт!

Сэтгэгдэл бичих