*这一系列文章来源于Fabien Potencier,基于Symfony1.4编写的Jobeet Tutirual。
今天我们就来完成第二天内容中Category的需求,实现Category的相关页面:
用户能够查看同一Category中的所有Job信息,并且Job信息能按照最近发布时间进行排序,并且每页显示20条Job信息
配置Category路由
首先,我们需要为Category页面添加易理解的URL路由。把下面的代码添加到路由文件的开头:
1 2 3 4 5 |
# src/Ibw/JobeetBundle/Resources/config/routing.yml # ... IbwJobeetBundle_category: pattern: /category/{slug} defaults: { _controller: IbwJobeetBundle:Category:show } |
为了得到转义过的Category::name属性值,我们需要在Category类中添加getSlug()方法:
1 2 3 4 5 6 7 8 9 10 11 12 |
// src/Ibw/JobeetBundle/Entity/Category.php use Ibw\JobeetBundle\Utils\Jobeet as Jobeet; class Category { // ... public function getSlug() { return Jobeet::slugify($this->getName()); } } |
添加链接到Category页面的URL
现在编辑JobController下的index.html.twig模板,让它能链接到对应的Category页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!-- src/Ibw/JobeetBundle/Resources/views/Job/index.html.twig --> <!-- some HTML code --> <h1><a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.name }}</a></h1> <!-- some HTML code --> </table> {% if category.morejobs %} <div class="more_jobs"> and <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.morejobs }}</a> more... </div> {% endif %} </div> {% endfor %} </div> {% endblock %} |
在上面的模板中,我们使用到了category.morejobs变量的值,所以我们需要定义它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// src/Ibw/JobeetBundle/Entity/Category.php class Category { // ... private $more_jobs; // ... public function setMoreJobs($jobs) { $this->more_jobs = $jobs >= 0 ? $jobs : 0; } public function getMoreJobs() { return $this->more_jobs; } } |
属于同一Category下的Job列表总行数减去首页中同一Category下的Job列表的行数的结果就是more_jobs属性中Job的数量(看代码)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// src/Ibw/JobeetBundle/Controller/JobController.php public function indexAction() { $em = $this->getDoctrine()->getManager(); $categories = $em->getRepository('IbwJobeetBundle:Category')->getWithJobs(); foreach($categories as $category) { $category->setActiveJobs($em->getRepository('IbwJobeetBundle:Job')->getActiveJobs($category->getId(), $this->container->getParameter('max_jobs_on_homepage'))); $category->setMoreJobs($em->getRepository('IbwJobeetBundle:Job')->countActiveJobs($category->getId()) - $this->container->getParameter('max_jobs_on_homepage')); } return $this->render('IbwJobeetBundle:Job:index.html.twig', array( 'categories' => $categories )); } |
我们需要在JobRepository类中加入countActiveJobs()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// src/Ibw/JobeetBundle/Repository/JobRepository.php // ... public function countActiveJobs($category_id = null) { $qb = $this->createQueryBuilder('j') ->select('count(j.id)') ->where('j.expires_at > :date') ->setParameter('date', date('Y-m-d H:i:s', time())); if($category_id) { $qb->andWhere('j.category = :category_id') ->setParameter('category_id', $category_id); } $query = $qb->getQuery(); return $query->getSingleScalarResult(); } // ... |
现在我们可以在浏览器中看到下面的结果:
创建CategoryController控制器
现在是时候创建 CategoryController控制器了。我们在src/Ibw/JobeetBundle/Controller目录下添加CategoryController.php文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/Ibw/JobeetBundle/Controller/CategoryController.php namespace Ibw\JobeetBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Ibw\JobeetBundle\Entity\Category; /** * Category controller * */ class CategoryController extends Controller { } |
和之前的JobController类似,我们可以使用doctrine:generate:crud
命令来生成控制器,但其中生成的90%代码是我们不需要的,因此我们选择手动创建一个新的控制器。
更新数据库
我们现在为Category添加一个slug列,同时把它加入到lifecycleCallbacks区块中,这样slug的值就能被自动填充了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# src/Ibw/JobeetBundle/Resources/config/doctrine/Category.orm.yml Ibw\JobeetBundle\Entity\Category: type: entity repositoryClass: Ibw\JobeetBundle\Repository\CategoryRepository table: category id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 255 unique: true slug: type: string length: 255 unique: true oneToMany: jobs: targetEntity: Job mappedBy: category manyToMany: affiliates: targetEntity: Affiliate mappedBy: categories lifecycleCallbacks: prePersist: [ setSlugValue ] preUpdate: [ setSlugValue ] |
删除Category实体(src/Ibw/JobeetBundle/Entity/Category.php)中的getSlug()方法,然后运行下面的命令更新Category实体:
1 |
php app/console doctrine:generate:entities IbwJobeetBundle |
现在我们可以看到Category.php中添加了下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// src/Ibw/JobeetBundle/Entity/Category.php // ... /** * @var string */ private $slug; /** * Set slug * * @param string $slug * @return Category */ public function setSlug($slug) { $this->slug = $slug; return $this; } /** * Get slug * * @return string */ public function getSlug() { return $this->slug; } |
修改setSlugValue()方法:
1 2 3 4 5 6 7 8 9 10 11 12 |
// src/Ibw/JobeetBundle/Entity/Category.php // ... class Category { // ... public function setSlugValue() { $this->slug = Jobeet::slugify($this->getName()); } } |
因为我们为Category实体类加入了slug列,所以我们需要更新数据表。现在我们需要删掉之前的数据库并重新创建,然后加载Fixture:
1 2 3 4 |
php app/console doctrine:database:drop --force php app/console doctrine:database:create php app/console doctrine:schema:update --force php app/console doctrine:fixtures:load |
Category页面
现在我们需要往showAction()方法中添加一些东西了。在CategoryController.php中添加下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// src/Ibw/JobeetBundle/Controller/CategoryController.php // ... public function showAction($slug) { $em = $this->getDoctrine()->getManager(); $category = $em->getRepository('IbwJobeetBundle:Category')->findOneBySlug($slug); if (!$category) { throw $this->createNotFoundException('Unable to find Category entity.'); } $category->setActiveJobs($em->getRepository('IbwJobeetBundle:Job')->getActiveJobs($category->getId())); return $this->render('IbwJobeetBundle:Category:show.html.twig', array( 'category' => $category, )); } // ... |
接下来我们创建show.html.twig模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!-- src/Ibw/JobeetBundle/Resources/views/Category/show.html.twig --> {% extends 'IbwJobeetBundle::layout.html.twig' %} {% block title %} Jobs in the {{ category.name }} category {% endblock %} {% block stylesheets %} {{ parent() }} <link rel="stylesheet" href="{{ asset('bundles/ibwjobeet/css/jobs.css') }}" type="text/css" media="all" /> {% endblock %} {% block content %} <div class="category"> <div class="feed"> <a href="">Feed</a> </div> <h1>{{ category.name }}</h1> </div> <table class="jobs"> {% for entity in category.activejobs %} <tr class="{{ cycle(['even', 'odd'], loop.index) }}"> <td class="location">{{ entity.location }}</td> <td class="position"> <a href="{{ path('ibw_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}"> {{ entity.position }} </a> </td> <td class="company">{{ entity.company }}</td> </tr> {% endfor %} </table> {% endblock %} |
包含一个Twig模板
你可能注意到了,我们在Category模块中的show.html.twig文件里创建Job列表的HTML代码和在Job模块中的index.html.twig文件里创建Job列表的HTML代码一样。请注意,这里已经有代码重复了,我们都知道代码重复不是件好事。如果我们需要重用模板中的一些代码,我们可以创建一个新的twig模板,然后在需要用到它们的模板中把它们包含进来即可。现在我们来创建list.html.twig文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- src/Ibw/JobeetBundle/Resources/views/Job/list.html.twig --> <table class="jobs"> {% for entity in jobs %} <tr class="{{ cycle(['even', 'odd'], loop.index) }}"> <td class="location">{{ entity.location }}</td> <td class="position"> <a href="{{ path('ibw_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}"> {{ entity.position }} </a> </td> <td class="company">{{ entity.company }}</td> </tr> {% endfor %} </table> |
我们可以在模板中使用include函数来替换我们之前的两个模板中出现过的
标签的内容:
1 2 |
<!--src/Ibw/JobeetBundle/Resources/views/Job/index.html.twig --> {{ include ('IbwJobeetBundle:Job:list.html.twig', {'jobs': category.activejobs}) }} |
1 2 |
<!--src/Ibw/JobeetBundle/Resources/views/Category/show.html.twig --> {{ include ('IbwJobeetBundle:Job:list.html.twig', {'jobs': category.activejobs}) }} |
分页列表
现在(作者写这篇教程的时间)Symfony2并没用为我们提供好的分页工具,那我们就来使用经典的方式来实现分页吧。首先我们来为IbwJobeetBundle_category路由添加分页参数。分页参数的默认值为1,因此它可以不是必需的:
1 2 3 4 5 6 |
# src/Ibw/JobeetBundle/Resources/config/routing.yml IbwJobeetBundle_category: pattern: /category/{slug}/{page} defaults: { _controller: IbwJobeetBundle:Category:show, page: 1 } # ... |
修改路由后我们需要清除缓存:
1 2 |
php app/console cache:clear --env=dev php app/console cache:clear --env=prod |
每页显示多少行Job信息的参数我们会在app/config/config.yml中定义:
1 2 3 4 5 6 |
# app/config/config.yml # ... parameters: max_jobs_on_homepage: 10 max_jobs_on_category: 20 |
修改JobRepository::getActiveJobs()方法,为它添加一个$offset参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// src/Ibw/JobeetBundle/Repository/JobRepository.php // ... public function getActiveJobs($category_id = null, $max = null, $offset = null) { $qb = $this->createQueryBuilder('j') ->where('j.expires_at > :date') ->setParameter('date', date('Y-m-d H:i:s', time())) ->orderBy('j.expires_at', 'DESC'); if($max) { $qb->setMaxResults($max); } if($offset) { $qb->setFirstResult($offset); } if($category_id) { $qb->andWhere('j.category = :category_id') ->setParameter('category_id', $category_id); } $query = $qb->getQuery(); return $query->getResult(); } // |
修改Category::showAction()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// src/Ibw/JobeetBundle/Controller/CategoryController.php public function showAction($slug, $page) { $em = $this->getDoctrine()->getManager(); $category = $em->getRepository('IbwJobeetBundle:Category')->findOneBySlug($slug); if (!$category) { throw $this->createNotFoundException('Unable to find Category entity.'); } $total_jobs = $em->getRepository('IbwJobeetBundle:Job')->countActiveJobs($category->getId()); $jobs_per_page = $this->container->getParameter('max_jobs_on_category'); $last_page = ceil($total_jobs / $jobs_per_page); $previous_page = $page > 1 ? $page - 1 : 1; $next_page = $page < $last_page ? $page + 1 : $last_page; $category->setActiveJobs($em->getRepository('IbwJobeetBundle:Job')->getActiveJobs($category->getId(), $jobs_per_page, ($page - 1) * $jobs_per_page)); return $this->render('IbwJobeetBundle:Category:show.html.twig', array( 'category' => $category, 'last_page' => $last_page, 'previous_page' => $previous_page, 'current_page' => $page, 'next_page' => $next_page, 'total_jobs' => $total_jobs )); } |
最后我们来修改模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<!-- src/Ibw/JobeetBundle/Resources/views/Category/show.html.twig --> {% extends 'IbwJobeetBundle::layout.html.twig' %} {% block title %} Jobs in the {{ category.name }} category {% endblock %} {% block stylesheets %} {{ parent() }} <link rel="stylesheet" href="{{ asset('bundles/ibwjobeet/css/jobs.css') }}" type="text/css" media="all" /> {% endblock %} {% block content %} <div class="category"> <div class="feed"> <a href="">Feed </a> </div> <h1>{{ category.name }}</h1> </div> {{ include ('IbwJobeetBundle:Job:list.html.twig', {'jobs': category.activejobs}) }} {% if last_page > 1 %} <div class="pagination"> <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug, 'page': 1 }) }}"> <img src="{{ asset('bundles/ibwjobeet/images/first.png') }}" alt="First page" title="First page" /> </a> <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug, 'page': previous_page }) }}"> <img src="{{ asset('bundles/ibwjobeet/images/previous.png') }}" alt="Previous page" title="Previous page" /> </a> {% for page in 1..last_page %} {% if page == current_page %} {{ page }} {% else %} <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug, 'page': page }) }}">{{ page }}</a> {% endif %} {% endfor %} <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug, 'page': next_page }) }}"> <img src="{{ asset('bundles/ibwjobeet/images/next.png') }}" alt="Next page" title="Next page" /> </a> <a href="{{ path('IbwJobeetBundle_category', { 'slug': category.slug, 'page': last_page }) }}"> <img src="{{ asset('bundles/ibwjobeet/images/last.png') }}" alt="Last page" title="Last page" /> </a> </div> {% endif %} <div class="pagination_desc"> <strong>{{ total_jobs }}</strong> jobs in this category {% if last_page > 1 %} - page <strong>{{ current_page }}/{{ last_page }}</strong> {% endif %} </div> {% endblock %} |
结果如下:
原文链接:http://www.intelligentbee.com/blog/2013/08/13/symfony2-jobeet-day-7-playing-with-the-category-page/
One comment