jobeet第十天:表单

*这一系列文章来源于Fabien Potencier,基于Symfony1.4编写的Jobeet Tutirual

从简单的联系表单(contact form)到带有很多个表单域的复杂表单,任何一个网站都会有表单的身影出现。对于一位Web开发者来说,编写表单是一个复杂而且乏味的工作之一:我们需要使用HTML代码把表单显示出来,然后需要为每个表单域添加验证规则,再把提交到服务器的表单值处理后保存到数据库中,(如果表单数据有错误)还需要把错误信息反馈给用户,而且还需要填充完用户没有错误的表单域等等…

在第三天的内容中,我门使用了doctrine:generate:crud命令为Job实体类生成了一个简单的CRUD控制器。同时也生成了一个Job表单,我们可以在/src/Ibw/JobeetBundle/Form/JobType.php文件中找到它。

自定义Job表单

Job表单是一个学习如何自定义表单的一个好例子。我们来一步步地学习怎样进行自定义表单。

首先,修改layout中的Post a Job,让它能够正确转向到Post页面:

修改JobController::createAction()ibw_job_show的路由参数,让它能够匹配我们在第五天中修改过的ibw_job_show路由:

Doctrine默认是按照数据表中的列(columns)来生成表单域的。但对于Job表单来说,有些表单域是不需要被最终用户(the end user)填写的。我们把Job表单编辑如下:

表单的设置要比数据表的设置更加精确。举个例子来说吧,email列在数据表中仅仅只要求是varchar类型的,而表单中则需要验证email要有正确的格式。在Symfony2中,表单验证被隐含在实体对象之中(比如Job)。换句话说,我们的问题不是验证表单(form)是否有效,而是验证Job对象是否有效(表单提交后的数据会被一一映射成Job对象中的属性值)。为了做到这点,我们在Resources/config目录下创建一个validation.yml文件:

尽管type列(column)在数据表中是varchar类型的,但我们想限制它的取值只能在:full timepart timefreelance三个值之中。

为了让上面的代码能够运行,我们需要在Job实体中添加下面的代码:

表单利用getTypes()方法生成Job可以选择的类型,getTypeValues()方法被用来验证type表单域值的有效性。

对于每个表单域,Symfony会为每个表单域自动生成一个label。我们能通过label选项来修改默认的label值:

我们同样需要为剩下的表单域添加验证约束:

这里约束url表单域值只能像http://www.sitename.domain或者https://www.sitename.domain这样的格式。

修改完validation.yml之后,我们需要清除cache

Symfony2处理上传文件

为了能够在表单中处理上传文件,我们会给Job实体添加一个新的file属性:

现在我们需要把logo文本域替换成文件域:

为了确保上传的文件是图片,我们需要添加图片文件的验证:

当表单提交之后,文件域会被映射成UploadedFile类的一个实例。我们能通过给它一个参数来改变上传文件存放的位置。在这之后我们将会看到Job类的logo属性会被设置成上传文件的名字。

我们需要创存放logo图片的目录(web/uploads/jobs),并且确保这个目录是可以写的。

尽管能有这种方式处理文件的上传,但我们有更好的方式,那就是使用Job实体。

首先在Job实体中添加下面的代码:

logo属性保存的值是上传文件的相对路径,它会被保存到数据库中。getAbsolutePath()方法返回的是上传文件的绝对路径,而getWebPath()方法是返回的是web路径,我们可以在模板中使用它。

我们会把数据库持久化和上传文件实现得具有“原子性(atomic)”:即如果不能成功保存Job实体到数据库或者文件上传失败,那么这两件事都将会失败(比如实体成功保存到数据库了,但文件上传失败了,这种情况属于操作失败,那么数据库保存的数据也必须撤销掉)。我们在当Doctrine成功保存实体后才把上传文件存在目录中。我们可以通过Job实体的lifecycle callback来完成这项操作。就像我们在第三天内容中所做的,我们在Job.orm.yml中添加preUploaduploadremoveUpload回调方法:

现在运行generate:entities命令为Job实体生成新的方法:

修改Job.php中新增的方法:

现在Job实体类已经能够完成我们需要的工作了:它会在保存到数据库之前生成一个唯一的文件名,保存数据库后就会把文件存放到目录下,当实体被删除的时候上传文件也将被删除。Job实体已经能够自动处理文件上传了,我们现在需要删除JobController中处理文件上传的代码:

 

表单模板

我们已经完成了表单类的自定义,现在我们需要把它显示出来。修改new.html.twig模板:

我们可以使用下面那一行代码来显示表单,但我们需要更多的自定义内容,所以我们选择手动来添加表单域。

 

form(form)会显示每个表单域,而且还会带有labelerror信息(如果有的话)。虽然这种方式简单,但是它不是很灵活。更多时候我们需要去自定义表单域的显示,以更好地控制它们的样式外观。

我们也使用一种叫做form theming的技术来自定义form errors的渲染。你可以参考Symfony2的官方文档

eidt.html.twig做同样的修改:

 

表单行为(Action

现在我们已经有了一个表单类(JobType)和一个利用它生成表单的模板。现在是时候让它能够做一点实际的行为了。Job表单被JobController中的四个方法所管理着:

  • newAction:显示一个空表单用来创建一个新job
  • createAction:处理表单(表单验证,表单填充)和利用用户提交的表单值实例化一个Job对象
  • editAction:显示一个已存在job的编辑表单
  • updateAction:处理表单(表单验证,表单填充)和利用用户提交的表单值更新已存在的Job对象

当你浏览/job/new page时,createForm()方法会实例化一个新的job表单实例,然后把这个表单实例传递给模板(newAction)。

当用户提交了表单(createAction),表单就绑定有用户提交的值(bind($request)方法),同时也会触发表单验证。一旦表单被绑定了,那么就可以使用isValid()方法验证表单是否有效:如果表单有效(返回true),那么Job数据就会被保存到数据库中($em->persist($entity)),然后用户会被重定向到Job数据的预览页面;如果表单无效,那么new.html.twig模板会被重新渲染,而且表单还会自动填充用户提交上来的表单值,同时还会显示出错误信息。修改一个已存在的Job和这个过程很相似。newedit行为唯一不同的是,需要被修改的Job对象是被作为crateForm()方法的第二个参数所使用。在模板中,这个对象的值的会被用来当做默认表单域的值。

我们也可以为creation表单定义默认的值。我们会传递一个预修改的Job对象给createForm()方法来设置type的默认值为full-time

 

使用Token来保护表单

现在一切都进展顺利,可是用户必须得填写token才能添加Job数据。通常来说,token应该是在这个Job数据被创建的时候自动生成的,我们不想让用户自己去提供这个唯一值的token。给Job实体的prePersist lifecycleCallbacks添加setTokenValue方法:

重新生成实体:

修改Job实体的setTokenValue()方法,给它添加生成token的逻辑代码:

删除表单中的token域:

删除/new.html.twigedit.html.twig中的token域:

 

删除validation.yml中的token域:

还记得第二天内容中的用户stories吗,只有用户知道Jobtoken才能对该Job信息进行修改。很好,修改或者删除一个Job数据是多么得简单呀,你只需要去猜那个URL中的token是什么值就可以办到了,你说这不是很简单(keng ren)吗?。修改Job信息的访问URL类似于/job/ID/edit,这里的IDJob的主键。

我们来修改路由,只有URL带上token才能修改和删除Job信息:

现在修改JobController,使用token来代替id

修改show.html.twig模板中的ibw_job_edit路由参数:

修改edit.html.twig模板中的ibw_job_update路由:

除了job_show_user路由外,其他和Job相关的路由都已经带上了token。现在,一个Job的URL样式应该是类似于这样的:http://jobeet.local/job/TOKEN/edit

 

预览页面

预览页面和Job页面显示的内容是一样的。唯一不同的是,预览页面是通过token访问的,而不是通过id

previewAction()(它和showAction()不同的是,它通过token来检索job,而不是通过id):

如果用户是通过带token的URL访问到页面的,那么我们就会在页面顶部显示一个admin栏。在show.html.twig的开头包含(include)一个模板,这个模板是用来显示admin栏的。同时删除底部的edit链接:

然后创建admin.html.twig模板:

这里虽然代码很多,但是都是简单并且容易理解的。

为了让模板的可读性更好,我们为Job实体添加一组简单的方法:

admin栏随着Job状态的不同显示出来的样式也会不同:

Day-10-admin-bar

Day-10-admin-badr-2

现在修改JobController中的createAction()updateAction(),让它们重定向到预览页面:

就像我们之前所说的,如果你知道token或者你是Jobeet的管理员的话,你就有权可以修改一条Job信息。但是我们现在去访问Job页面的话,我们可以看到页面上有一个Edit链接,这样的用户体验让人感觉不好。我们来修改show.html.twig,删除Edit链接:

 

Job激活和发布

Job页面的上部分有一个发布Job信息的链接,这个链接需要被指定到publishAction()上。我们先创建一个新的路由:

现在我们来修改Publish链接(我们在这里使用表单,就像删除一个Job一样,它会发送一个POST请求):

最后一步是创建publishAction(),同时修改previewAction()给模板传递一个publish表单:

PublishAction()方法使用的publish()可以在Job实体中定义:

现在我们可以在浏览中测试publish功能了。

但现在我们依然还有问题需要去修正。那些未被激活的Job数据是不可以被访问到的,也不能出现在首页中。我们需要修改JobRepository

修改CategoryRepository中的getWithJobs()方法:

好了,今天就这些了。你可以在浏览器中测试一下今天的实现的内容。所有未被激活的Job数据是不会显示到首页中的,即使你知道URL链接,它们也是不可以被访问到的。不过,如果你知道带token的URL的话就可以访问到,在这种情况下,Job预览页面顶部会显示出admin栏。

 

原文链接:http://www.intelligentbee.com/blog/2013/08/16/symfony2-jobeet-day-10-the-forms/

One comment

发表评论