2009-06-18

内嵌表单详解(How to Embed Forms in Symfony 1.2 Admin Generator 中文版)

类归于: symfony — 标签:, , maker @ 23:31

译者:Maker.Wang[at]gmail.com
原文:http://sandbox-ws.com/how-to-embed-forms-in-symfony-12-admin-generator

译者注: Embed form是一个被忽略了的却很实用的技术,可能是由于embed form是新功能的缘故,相关的文档非常少,特别是中文文档,很早以前就听说了embed form,但一直没有机会实际应用, 昨天项目的新需求让我又想起了embed form, 所以又翻出了以前看过的文档, 仔细复习一遍, 同时也翻译出来分享个各位战友们。这是小弟的第一篇译文,难免有错误和遗漏,希望大家多包涵,多提宝贵意见。 – maker 2009.6.19

第一部分. 一对一关联内嵌表单

Symfony 1.2 添加了很多令人兴奋的新特性,并已经成为了一个很好很强大的PHP开发框架。 其中的一个特性是将一个Form嵌入另一个Form中。那么这意味着什么呢?

第一个模型如下图所示:

company_contact 上图是一个公司和联系方式的一对一关系模型。

这是我们的 schema.yml:

  1. propel:
  2. _attributes:
  3. package: lib.model
  4. defaultIdMethod: native
  5. company:
  6. _attributes: { phpName: Company }
  7. id: { type: INTEGER, size: ‘11′, primaryKey: true, autoIncrement: true, required: true }
  8. name: { type: VARCHAR, size: ‘255′, required: true }
  9. contact_id: { type: INTEGER, size: ‘11′, required: false, foreignTable: contact, foreignReference: id, onDelete: SET NULL, onUpdate: RESTRICT }
  10. _indexes: { company_FI_1: [contact_id] }
  11. contact:
  12. _attributes: { phpName: Contact }
  13. id: { type: INTEGER, size: ‘11′, primaryKey: true, autoIncrement: true, required: true }
  14. first_name: { type: VARCHAR, size: ‘255′, required: true }
  15. last_name: { type: VARCHAR, size: ‘255′, required: true }
  16. company_id: { type: INTEGER, size: ‘11′, required: false, foreignTable: company, foreignReference: id, onDelete: SET NULL, onUpdate: RESTRICT }
  17. _indexes: { contact_FI_1: [company_id] }

要注意的是这里的两个外键并不是必须存在(required)的,但是必须有了这两个外键我们才能将Contact Form嵌入到Company Form。

现在我们已经有了Schema, 让我们生成sql语句,模型(models), 表单(forms), 过滤器(filters), 并且创建数据表。

  1. $ php symfony propel:build-sql
  2. $ php symfony propel:build-model
  3. $ php symfony propel:build-forms
  4. $ php symfony propel:build-filters
  5. $ php symfony propel:insert-sql –env=dev

译者注:上面这组命令我们也可以简单的使用一个命令来完成

  1. $ php symfony propel:build-all

现在我们使用symfony中的admin generator来生成一个模块, 来看看如何生成和操作他们之间的关联。

  1. $ php symfony propel:generate-admin backend Company

我们生成了如下图所示的模块:

company_1现在如果我们点击“New”链接我们将看到界面如下:

company_2

注意外键”contact_id”被admin generator处理成一个下拉列表,当然这不是我们想要的结果,我们需要在新建Company的同时创建Contact. 感谢symfony 1.2 支持表单内嵌, 这会使我们的工作变得非常简单。

首先打开 “CompanyForm.class.php” 并且输入代码如下:

  1. public function configure() {
  2. // get Contact model object
  3. $contact = $this->getObject()->getContact();
  4. // contact object is null
  5. if (is_null($contact)) {
  6. // create a new Contact object
  7. $contact = new Contact();
  8. // set the copmany of the newly created object to the current company
  9. $contact->setCompany($this->getObject());
  10. // set the contact of the current company
  11. $this->getObject()->setContact($contact);
  12. }
  13. // create a new contact form
  14. $contact_form = new ContactForm($contact);
  15. // embed the contact form in the current company form
  16. $this->embedForm(‘contact’, $contact_form);
  17. // remove the contact_id from the form
  18. unset($this['contact_id']);
  19. }

接下来,打开ContactForm.class.php 并输入代码如下:

  1. public function configure() {
  2. unset($this['company_id']);
  3. }

现在修改模型类Company.php,在删除Contact的同时删除关联的Company

  1. public function delete(PropelPDO $con = null) {
  2. $this->getContact()->delete($con);
  3. parent::delete($con);
  4. }

最后一点也是很重要的,编辑generator.yml如下(这一步不是必须得) :

  1. generator:
  2. class: sfPropelGenerator
  3. param:
  4. model_class:           Company
  5. theme:                 admin
  6. non_verbose_templates: true
  7. with_show:             false
  8. singular:              ~
  9. plural:                ~
  10. route_prefix:          company
  11. with_propel_route:     1
  12. config:
  13. actions: ~
  14. fields:
  15. contact_id: { label: Company Contact }
  16. list:
  17. display: [=name, contact]
  18. filter:  ~
  19. form:    ~
  20. edit:    ~
  21. new:     ~

现在进入新建”Company”页面你会看到如图所示:

company_3尝试添加,编辑和删除一些记录以便确认一切是正确的。

company_4

company_5

第二部分. 一对多关联的内嵌表单

在文章的第一部分我们讲解了如何实一对一关联的表单内嵌。但是我们经常需要在编辑父对象的时候编辑或者添加子对象, 这并不是我们要使用admin generator的原因,总之在这部分文章里我们将要讲解一对多关系的表单内嵌。

在开始之前,我们看到的是最终要实现的CategoryForm。

embed_2

本文需要的文件

Ok, 我们开始吧,考虑下面这个schema:

  1. category:
  2. _attributes: { phpName: Category }
  3. id: { type: INTEGER, size: ‘11′, primaryKey: true, autoIncrement: true, required: true }
  4. name: { type: VARCHAR, size: ‘255′, required: true }
  5. created_at: { type: TIMESTAMP, required: false }
  6. updated_at: { type: TIMESTAMP, required: false }
  7. subcategory:
  8. _attributes: { phpName: Subcategory }
  9. id: { type: INTEGER, size: ‘11′, primaryKey: true, autoIncrement: true, required: true }
  10. name: { type: VARCHAR, size: ‘255′, required: true }
  11. category_id: { type: INTEGER, size: ‘11′, required: true, foreignTable: category, foreignReference: id, onDelete: CASCADE, onUpdate: RESTRICT }
  12. created_at: { type: TIMESTAMP, required: false }
  13. updated_at: { type: TIMESTAMP, required: false }

为了实现想要的效果,我们需要需要采取以下步骤

  1. 修改CategoryForm去包含当前分类所有的子分类的内嵌表单。
  2. 修改Widget中的子分类的name
  3. 修改CategoryForm添加一个空白的创建新子分类的表单。
  4. 重写sfForm类的bind方法,如果name域是空白则跳过保存(saving)和验证(validating)新子分类的表单。
  5. 在SubcategoryForm去掉全部的域(fields)

为当前分类插入全部子分类的内嵌表单[第1,2,3步]

  1. // lib/forms/CategoryForm.class.php
  2. public function configure() {
  3. // remove timestamps
  4. unset($this['created_at'], $this['updated_at']);
  5. // embed forms only when editing
  6. if (!$this->isNew()) {
  7. // embed all subcategory forms
  8. foreach ($this->getObject()->getSubcategorys() as $subcategory) {
  9. // create a new subcategory form for the current subcategory model object
  10. $subcategory_form = new SubcategoryForm($subcategory);
  11. // embed the subcategory form in the main category form
  12. $this->embedForm(’subcategory’.$subcategory->getId(), $subcategory_form);
  13. // set a custom label for the embedded form
  14. $this->widgetSchema['subcategory'.$subcategory->getId()]->setLabel(‘Subcategory: ’.$subcategory->getName());
  15. // change the name widget to sfWidgetFormInputDelete
  16. $this->widgetSchema['subcategory'.$subcategory->getId()]['name'] = new sfWidgetFormInputDelete(array(
  17. ‘url’ => ‘category/deleteSubcategory’, // required
  18. ‘model_id’ => $subcategory->getId(), // required
  19. ‘confirm’ => ‘Sure???’, // optional
  20. ));
  21. }
  22. // create a new subcategory form for a new subcategory model object
  23. $subcategory_form = new SubcategoryForm();
  24. // embed the subcategory form in the main category form
  25. $this->embedForm(’subcategory’, $subcategory_form);
  26. // set a custom label for the embedded form
  27. $this->widgetSchema['subcategory']->setLabel(‘New Subcategory’);
  28. }
  29. }

重写bind方法

  1. public function bind(array $taintedValues = null, array $taintedFiles = null) {
  2. // remove the embedded new form if the name field was not provided
  3. if (is_null($taintedValues['subcategory']['name']) || strlen($taintedValues['subcategory']['name']) === 0 ) {
  4. unset($this->embeddedForms['subcategory'], $taintedValues['subcategory']);
  5. // pass the new form validations
  6. $this->validatorSchema['subcategory'] = new sfValidatorPass();
  7. else {
  8. // set the category of the new subcategory form object
  9. $this->embeddedForms['subcategory']->getObject()->setCategory($this->getObject());
  10. }
  11. // call parent bind method
  12. parent::bind($taintedValues, $taintedFiles);
  13. }

在SubcategoryForm中的域

  1. public function configure(){
  2. unset($this['created_at'], $this['updated_at'], $this['category_id']);
  3. }

现在我们在分类模块下创建deleteSubcategory动作

  1. // apps/backend/modules/category/actions/actions.class.php
  2. public function executeDeleteSubcategory(sfWebRequest $request) {
  3. $sub_category = SubcategoryPeer::retrieveByPk($request->getParameter(‘id’));
  4. $sub_category->delete();
  5. $this->redirect(‘@category_edit?id=’.$sub_category->getCategory()->getId());
  6. }

就是这样了,我希望你会喜欢这篇文章。

第三部分. 内嵌表单的本地化

在本系列文章的上一部分我们介绍了在父对象中编辑多个子对象的方法。 在本部分钟我们将给子对象添加本地化行为, 为了更清楚的知道我们要做的事先看一下最终的效果。

embed_i18n

在开始之前,如果你没准备好,我强烈建议你去阅读一下第1,2部分。 OK, 我们开始吧,我们将要修改以下文件:

  • CategoryForm.class.php 分类模型的表单
  • SubcategoryI18nForm.class.php 子分类模型的本地化模型的表单
  • Subcategory.class.php 子分类模型

本文的附件

Schema.yml

  1. category:
  2. _attributes: { phpName: Category }
  3. id: { type: INTEGER, size: ‘11′, primaryKey: true, autoIncrement: true, required: true }
  4. name: { type: VARCHAR, size: ‘255′, required: true }
  5. created_at: { type: TIMESTAMP, required: false }
  6. updated_at: { type: TIMESTAMP, required: false }
  7. subcategory:
  8. _attributes: { phpName: Subcategory, isI18N: true, i18nTable: subcategory_i18n }
  9. id:          { type: integer, required: true, primaryKey: true, autoincrement: true }
  10. category_id: { type: INTEGER, size: ‘11′, required: true, foreignTable: category, foreignReference: id, onDelete: CASCADE}
  11. created_at: { type: TIMESTAMP, required: false }
  12. updated_at: { type: TIMESTAMP, required: false }
  13. subcategory_i18n:
  14. _attributes: { phpName: SubcategoryI18n }
  15. id:          { type: integer, required: true, primaryKey: true, foreignTable: subcategory, foreignReference: id, onDelete: CASCADE }
  16. culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
  17. name:        { type: varchar, size: 50 }

SubcategoryI18nForm.class.php

  1. public function configure() {
  2. unset($this['culture'], $this['id']);
  3. }

Subcategory.class.php

  1. public function getI18nObject($culture = ‘en’) {
  2. $this->setCulture($culture);
  3. $i18ns = $this->getSubcategoryI18ns();
  4. if (isset($i18ns[0])) {
  5. return $i18ns[0];
  6. }
  7. return null;
  8. }

CategoryForm.class.php

  1. public function configure() {
  2. // remove timestamps
  3. unset($this['created_at'], $this['updated_at']);
  4. // embed forms only when editing
  5. if (!$this->isNew()) {
  6. $user_culture = sfContext::getInstance()->getUser()->getCulture();
  7. // embed all subcategory forms
  8. foreach ($this->getObject()->getSubcategorys() as $subcategory) {
  9. // get the subcategory_i18n model object relative to the current user culture
  10. $subcategory_i18n_object = $subcategory->getI18nObject($user_culture);
  11. // create a new subcategory_i18n form for the current subcategory model object
  12. $subcategory_i18n_form = new SubcategoryI18nForm($subcategory->getI18nObject(‘en’));
  13. // get widget schema of subcategory_i18n form
  14. $subcategory_i18n_form_widget_schema = $subcategory_i18n_form->getWidgetSchema();
  15. // set the input delete widget
  16. $subcategory_i18n_form_widget_schema['name'] = new sfWidgetFormInputDelete(array(
  17. ‘url’ => ‘category/deleteSubcategory’, // required
  18. ‘model_id’ => $subcategory->getId(), // required
  19. ‘confirm’ => ‘Sure???’, // optional
  20. ));
  21. // create a new subcategory form for the current subcategory model object
  22. $subcategory_form = new SubcategoryForm($subcategory);
  23. // embed the i18n form
  24. $subcategory_form->embedForm(’subcategory_i18n’.$subcategory_i18n_object->getId(), $subcategory_i18n_form);
  25. // subcategory form widget schema
  26. $subcategory_form_widget_schema = $subcategory_form->getWidgetSchema();
  27. // disable label
  28. $subcategory_form_widget_schema['subcategory_i18n'.$subcategory_i18n_object->getId()]->setLabel(false);
  29. // embed the subcategory form in the main category form
  30. $this->embedForm(’subcategory’.$subcategory->getId(), $subcategory_form);
  31. // set a custom label for the embedded form
  32. $this->widgetSchema['subcategory'.$subcategory->getId()]->setLabel(‘Subcategory: ’.$subcategory->getName());
  33. }
  34. // create a new subcategory form for a new subcategory model object
  35. $subcategory_form = new SubcategoryForm();
  36. // create a new subcategory form for a new subcategory_i18n model object
  37. $subcategory_i18n_form = new SubcategoryI18nForm();
  38. // embed the subcategory_i18n form in the parent subcategory form
  39. $subcategory_form->embedForm(’subcategory_i18n’, $subcategory_i18n_form);
  40. // subcategory form widget schema
  41. $subcategory_form_widget_schema = $subcategory_form->getWidgetSchema();
  42. // disable label
  43. $subcategory_form_widget_schema['subcategory_i18n']->setLabel(false);
  44. // embed the subcategory form in the main category form
  45. $this->embedForm(’subcategory’, $subcategory_form);
  46. // set a custom label for the embedded form
  47. $this->widgetSchema['subcategory']->setLabel(‘New Subcategory’);
  48. }
  49. }
  50. public function bind(array $taintedValues = null, array $taintedFiles = null) {
  51. if(!$this->isNew()) {
  52. $user_culture = sfContext::getInstance()->getUser()->getCulture();
  53. // remove the embedded new form if the name field was not provided
  54. if (is_null($taintedValues['subcategory']['subcategory_i18n']['name']) || strlen($taintedValues['subcategory']['subcategory_i18n']['name']) === 0 ) {
  55. unset($this->embeddedForms['subcategory'], $taintedValues['subcategory']);
  56. // pass the new form validations
  57. $this->validatorSchema['subcategory'] = new sfValidatorPass();
  58. else {
  59. // set the category of the new subcategory form object
  60. $this->embeddedForms['subcategory']->getObject()->setCategory($this->getObject());
  61. // get subcategory embedded forms
  62. $subcategory_embedded_forms = $this->embeddedForms['subcategory']->getEmbeddedForms();
  63. // set subcategory parent of the subcategory_i18n model object
  64. $subcategory_embedded_forms['subcategory_i18n']->getObject()->setSubcategory($this->embeddedForms['subcategory']->getObject());
  65. // set the culture of the subcategory_i18n model object
  66. $subcategory_embedded_forms['subcategory_i18n']->getObject()->setCulture($user_culture);
  67. }
  68. }
  69. // call parent bind method
  70. parent::bind($taintedValues, $taintedFiles);
  71. }

正如你所看到的,这是功能需要你复制和粘贴大量的代码,这就是为什么我在使用扩展的Admin Generator,  为了当你生成管理模块的时候有此功能可用。

2009-05-25

怎么在app中构造其它app的链接(二)

类归于: symfony — 标签:zhuozi @ 17:15

写一个class

// apps/backend/config/backendConfiguration.class.php
class backendConfiguration extends sfApplicationConfiguration
{
protected $frontendRouting = null;
public function generateFrontendUrl($name, $parameters = array())
{
return 'http://frontend.example.com'.$this->getFrontendRouting()
->generate($name, $parameters);
}
public function getFrontendRouting()
{
if (!$this->frontendRouting)
{
$this->frontendRouting = new sfPatternRouting(new sfEventDispatcher());
$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').
'/frontend/config/routing.yml'));
$this->frontendRouting->setRoutes($routes);
}
return $this->frontendRouting;
}
}

在action中可以用下面的动作,就可以跳到其它的app中的动作,下面的‘hello’,是frontend/config配置文件routing中的一条路由规则

$this->redirect($this->getContext()->getConfiguration()->
generateFrontendUrl('hello', array('name' => 'Bar')));


function link_to_frontend($name, $parameters)
{
return sfProjectConfiguration::getActive()->
generateFrontendUrl($name, $parameters);
}

2009-05-23

怎么在app中构造其它app的链接(一)

类归于: CentOS/Redhat, symfony — 标签:zhuozi @ 16:44

最近开发的一个项目要求我们要在一个app中构造另一个app中的链接并使用。

我们现在有一个管理平台,app名字   admin, 我们还有一个PC展示平台 app名字 pc

我们知道使用symfony中链接辅助函数  url_for()  可以生成我们想要的URL,可是,默认的情况下,这种生成出来的URL是显示前端控制器名字的(控制器名字根据所在开发模式的不同,显示的也不一样)。

比如: 我们在admin这个平台使用  url_for(’member/create’)    生成的url是   admin_dev.php/member/create, 很显然这种URL不是我们所期望的,我们不希望还要去解析判断。

在apps/myapps/settings.yml 中有一项设置  no_script_name: off  这项是控制时候在URL中显示前端控制器的名字。

当我们把它开启的时候,再使用url_for(’member/create’)的时候,就会生成  member/create, 这样就会得到没有前端控制器名字的URL。

其实,最理想的状态是,我在使用某一个链接辅助函数的时候,直接就可以获得某一个平台的URL,比如在admin的平台返回给我一个  http://www.foolbirds.com/pc_dev.php/article/show  这样的链接。 可是找了一下下午也没有找到一个,或许需要自己写一个构造这样的URL来满足项目的需求了。

2009-05-11

如何使用swift发送邮件

类归于: symfony — 标签:, kthiz2006 @ 16:24

首先在项目的lib下建立vendor的文件夹
然后
svn checkout http://swiftmailer.svn.sourceforge.net/svnroot/swiftmailer/tags/php5/3.3.3/lib/ swift
接着把svn剪出的文件夹里的文件放到vendor目录下.
这样就可以使用swift发送邮件了
下面是一个简单的例子

<?php
try
{
// Create the mailer and message objects
$mailer = new Swift(new Swift_Connection_NativeMail());
$message = new Swift_Message('Mail\'s subject', $mailBody, 'text/html');
// Send
$mailer->send($message, $mailTo, $mailFrom);
$mailer->disconnect();
}
catch (Exception $e)
{
$mailer->disconnect();
// handle errors here
}
?>

2009-05-06

symfony1.2下的命令行程序(batch)

类归于: symfony — 标签:, , maker @ 15:09

这里说的命令行程序就是在命令行(或者叫shell)下执行的程序, 因为我们多数时间是用浏览器来访问symfony程序的, 而有些时候, 我们也需要在shell下来执行我们的程序, 比如定时任务.

在网上一番搜索并没有找到相关内容, 在1.0的<The Definitive Guide To symfony>里提到了一个init-batch的命令, 但是这个命令在1.2中已经不存在了.

后来终于在symfony命令中发现了generate:task命令, 这个命令的帮助文档是这样写的.

语法:
symfony generate:task [--dir="..."] [--use-database="..."] [--brief-description="..."] task_name

参数:
task_name The task name (can contain namespace)

选项:
–dir The directory to create the task in (default: lib/task)
–use-database Whether the task needs model initialization to access database (default: propel)
–brief-description A brief task description (appears in task list)

描述:
The generate:task creates a new sfTask class based on the name passed as
argument:

./symfony generate:task namespace:name

The namespaceNameTask.class.php skeleton task is created under the lib/task/
directory. Note that the namespace is optional.

If you want to create the file in another directory (relative to the project
root folder), pass it in the –dir option. This directory will be created
if it does not already exist.

./symfony generate:task namespace:name –dir=plugins/myPlugin/lib/task

If you want the task to default to a connection other than propel, provide
the name of this connection with the –use-database option:

./symfony generate:task namespace:name –use-database=main

The –use-database option can also be used to disable database
initialization in the generated task:

./symfony generate:task namespace:name –use-database=false

You can also specify a description:

./symfony generate:task namespace:name –brief-description=”Does interesting things”

简单来说, 这个命令是用来创建命令行任务的, 我们来做一个简单的测试.

./symfony generate:task test

这样我们就生成了test命令, 我们可以象下面这样使用test命令

./symfony test

虽然什么也没有输出, 但是没有报错说明命令存在. 如果要给命令添加动作需要修改 /lib/task/testTask.class.php

我们写一个简单的例子

// add your code here
echo "it runing\n";
$users = UserPeer::doSelect(new Criteria);
echo count($users);

这段代码执行了数据库操作, 这说明在task中类都是自动在载的, 详细阅读testTask.class.php中的代码我们会发现task还支持很多功能, 设置参数, 命名空间等等.

(未完待续)

« 较近文章早前文章 »

WordPress 所驱动