<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Symfony中文教程 &#187; sonataadmin</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/tag/sonataadmin/feed" rel="self" type="application/rss+xml" />
	<link>http://www.newlifeclan.com/symfony</link>
	<description>站在巨人肩膀上的phpweb框架</description>
	<lastBuildDate>Fri, 12 Dec 2025 00:58:27 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.0.38</generator>
	<item>
		<title>jobeet第十二天:后台管理工具包-Sonata Admin</title>
		<link>http://www.newlifeclan.com/symfony/archives/374</link>
		<comments>http://www.newlifeclan.com/symfony/archives/374#comments</comments>
		<pubDate>Fri, 27 Mar 2015 08:07:51 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[实战教程]]></category>
		<category><![CDATA[jobeet]]></category>
		<category><![CDATA[sonataadmin]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=374</guid>
		<description><![CDATA[<p>*这一系列文章来源于Fabien Potencier，基于Symfony1.4编写的Jobeet Tutiru [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/374">jobeet第十二天:后台管理工具包-Sonata Admin</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>*这一系列文章来源于Fabien Potencier，基于Symfony1.4编写的<a href="http://symfony.com/legacy/doc/jobeet?orm=Doctrine">Jobeet Tutirual</a>。</p>
<p>在第十一天中，我们给<em>Jobeet</em>添加了一些测试，这个应用程序已经完全能够被求职者（seekers）和职位发布者（posters）使用了。现在是时候考虑我们应用程序的<em>admin</em>部分了。今天还好有<a href="http://sonata-project.org/bundles/admin/2-0/doc/index.html">Sonata Admin Bundle</a>的帮助，我们会使用它开发出一个完整<em>Jobeet</em>后台管理接口，（这个过程）用不到一个小时。</p>
<p><span id="more-374"></span></p>
<h2>安装<em>Sonata Admin Bundle</em></h2>
<p>下载<em>SonataAdminBundle</em>和它的依赖到<em>vendor</em>目录下：</p><pre class="crayon-plain-tag">php composer.phar require sonata-project/admin-bundle</pre><p>为了安装最新版本的<em>SonataAdminBundle</em>和依赖，这个使用*作为输入。</p><pre class="crayon-plain-tag">ibw@ubuntu:/var/www/jobeet$ php composer.phar require sonata-project/admin-bundle
Please provide a version constraint for the sonata-project/admin-bundle requirement: *</pre><p>我们同样需要安装<em>SonataDoctrineORMADminBundle</em>：</p><pre class="crayon-plain-tag">php composer.phar require sonata-project/doctrine-orm-admin-bundle</pre><p>现在我们需要声明新的<em>bundle</em>和依赖，修改<em>AppKernel.php</em>文件，添加下面代码到文件中：</p><pre class="crayon-plain-tag">// app/AppKernel.php
// ...
public function registerBundles()
{
    $bundles = array(
        // ...
        new Sonata\AdminBundle\SonataAdminBundle(),
        new Sonata\BlockBundle\SonataBlockBundle(),
        new Sonata\jQueryBundle\SonatajQueryBundle(),
        new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
        new Knp\Bundle\MenuBundle\KnpMenuBundle(),
    );
}

// ...</pre><p>修改<em>config.yml</em>，把下面代码添加到文件末尾：</p><pre class="crayon-plain-tag"># app/config/config.yml
# ...
sonata_admin:
    title: Jobeet Admin

sonata_block:
    default_contexts: [cms]
    blocks:
        sonata.admin.block.admin_list:
            contexts:   [admin]

        sonata.block.service.text:
        sonata.block.service.action:
        sonata.block.service.rss:</pre><p>在<em>config.yml</em>中找到<em>translator</em>键。如果它被注释掉了，那么请不要注释它。</p><pre class="crayon-plain-tag"># app/config/config.yml
# ...
framework:
    # ...
    translator: { fallback: %locale%}
    # ...
#...</pre><p>为了能让应用程序跑起来，我们需要导入（import）<em>admin routes</em>：</p><pre class="crayon-plain-tag"># app/config/routing.yml
admin:
    resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

# ...</pre><p>现在我们从<em>bundle</em>中安装资源：</p><pre class="crayon-plain-tag">php app/console assets:install web --symlink</pre><p>别忘记把<em>cache</em>清除掉：</p><pre class="crayon-plain-tag">php app/console cache:clear --env=dev
php app/console cache:clear --env=prod</pre><p>现在我们应该可以通过URL：http://jobeet.local/app_dev.php/admin/dashboard访问放到<em>admin</em>面板了。</p>
<blockquote><p>以上的安装需要一步步进行，不然的话可能会报一些Bundle未找到的异常，还有就是目录必须是可写的。可能是SonataAdminBundle更新后的原因，如果在安装过程中遇到需要CoreBundle的问题，那么试着按照提示进行操作，但问题都不大。这里附上一个解决问题的链接：<a href="https://github.com/sonata-project/SonataAdminBundle/issues/1832">https://github.com/sonata-project/SonataAdminBundle/issues/1832</a></p></blockquote>
<h2></h2>
<h2><em>CRUD</em>控制器</h2>
<p><em>CRUD</em>控制器包含了基础的<em>CRUD</em>操作。它是通过控制器名称映射到一个正确的<em>Admin</em>类的一个实例。我们可以按照项目的需求来重写任何或者所有的<em>action</em>。控制器使用<em>Admin</em>类来构造不同的操作。在控制器中可以通过<em>configuration</em>属性来访问<em>Admin</em>对象。</p>
<p>现在我们来为每个实体都创建一个控制器。首先是<em>Category</em>实体：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/CategoryAdminController.php
namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class CategoryAdminController extends Controller
{
    // Your code will be here
}</pre><p>然后是<em>Job</em>实体：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/JobAdminController.php
namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class JobAdminController extends Controller
{
    // Your code will be here
}</pre><p>&nbsp;</p>
<h2>创建<em>Admin</em>类</h2>
<p><em>Admin</em>类代表的是模型的映射和管理页面（表单，列表，页面显示（show））的部分。为模型创建<em>Admin</em>类的最简单方式是去继承<em>Sonata\AdminBundle\Admin\Admin</em>类。我们会在<em>Admin</em>文件夹中创建<em>Admin</em>类。我们先创建<em>Admin</em>目录，然后为<em>Category</em>创建<em>Admin</em>类：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Admin/CategoryAdmin.php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // Your code will be here
}</pre><p>&nbsp;</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Admin/JobAdmin.php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Ibw\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // Your code will be here
}</pre><p>现在我们需要把每个<em>admin</em>类添加到<em>services.yml</em>配置文件中：</p><pre class="crayon-plain-tag"># src/Ibw/JobeetBundle/Resources/config/services.yml
services:
    ibw.jobeet.admin.category:
        class: Ibw\JobeetBundle\Admin\CategoryAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Categories }
        arguments:
            - ~
            - Ibw\JobeetBundle\Entity\Category
            - 'IbwJobeetBundle:CategoryAdmin'

    ibw.jobeet.admin.job:
        class: Ibw\JobeetBundle\Admin\JobAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Jobs }
        arguments:
            - ~
            - Ibw\JobeetBundle\Entity\Job
            - 'IbwJobeetBundle:JobAdmin'</pre><p>现在我们可以在管理面板页看到<em>Jobeet</em>分组了，还有<em>Job</em>和<em>Category</em>模块（modules），它们都有各自的<em>Add new</em>和<em>List</em>链接。</p>
<p><img class="alignnone size-large wp-image-375" src="http://www.newlifeclan.com/symfony/wp-content/uploads/sites/2/2015/03/12-01-1024x292.jpg" alt="12-01" width="780" height="222" /></p>
<h2>配置<em>Admin</em>类</h2>
<p>如果我们去点击<em>Add new</em>或者<em>List</em>链接，我们什么都看不到。那是因为我们还没有为<em>list</em>和<em>form</em>配置字段。我们来做个基础的配置，先从<em>Category</em>开始：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Admin/CategoryAdmin.php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // setup the default sort column and order
    protected $datagridValues = array(
        '_sort_order' =&gt; 'ASC',
        '_sort_by' =&gt; 'name'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            -&gt;add('name')
            -&gt;add('slug')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            -&gt;add('name')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            -&gt;addIdentifier('name')
            -&gt;add('slug')
        ;
    }
}</pre><p>现在是<em>Job</em>：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Admin/JobAdmin.php
namespace Ibw\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Ibw\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // setup the defaut sort column and order
    protected $datagridValues = array(
        '_sort_order' =&gt; 'DESC',
        '_sort_by' =&gt; 'created_at'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            -&gt;add('category')
            -&gt;add('type', 'choice', array('choices' =&gt; Job::getTypes(), 'expanded' =&gt; true))
            -&gt;add('company')
            -&gt;add('file', 'file', array('label' =&gt; 'Company logo', 'required' =&gt; false))
            -&gt;add('url')
            -&gt;add('position')
            -&gt;add('location')
            -&gt;add('description')
            -&gt;add('how_to_apply')
            -&gt;add('is_public')
            -&gt;add('email')
            -&gt;add('is_activated')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            -&gt;add('category')
            -&gt;add('company')
            -&gt;add('position')
            -&gt;add('description')
            -&gt;add('is_activated')
            -&gt;add('is_public')
            -&gt;add('email')
            -&gt;add('expires_at')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            -&gt;addIdentifier('company')
            -&gt;add('position')
            -&gt;add('location')
            -&gt;add('url')
            -&gt;add('is_activated')
            -&gt;add('email')
            -&gt;add('category')
            -&gt;add('expires_at')
            -&gt;add('_action', 'actions', array(
                'actions' =&gt; array(
                    'view' =&gt; array(),
                    'edit' =&gt; array(),
                    'delete' =&gt; array(),
                )
            ))
        ;
    }

    protected function configureShowField(ShowMapper $showMapper)
    {
        $showMapper
            -&gt;add('category')
            -&gt;add('type')
            -&gt;add('company')
            -&gt;add('webPath', 'string', array('template' =&gt; 'IbwJobeetBundle:JobAdmin:list_image.html.twig'))
            -&gt;add('url')
            -&gt;add('position')
            -&gt;add('location')
            -&gt;add('description')
            -&gt;add('how_to_apply')
            -&gt;add('is_public')
            -&gt;add('is_activated')
            -&gt;add('token')
            -&gt;add('email')
            -&gt;add('expires_at')
        ;
    }
}</pre><p>对于<em>show</em>操作，我们使用自定义的模板来显示公司的<em>logo</em>属性：</p><pre class="crayon-plain-tag">&lt;!-- src/Ibw/JobeetBundle/Resources/views/JobAdmin/list_image.html.twig --&gt;
&lt;tr&gt;
    &lt;th&gt;Logo&lt;/th&gt;
    &lt;td&gt;&lt;img src="{{ asset(object.webPath) }}" /&gt;&lt;/td&gt;
&lt;/tr&gt;</pre><p>通过上面的操作，我们为<em>Job</em>和<em>Category</em>创建了基础的管理模块。我们可以发现它们有以下功能：</p>
<ul class="task-list">
<li>列表分页显示</li>
<li>列表可以排序</li>
<li>列表可以过滤</li>
<li>可以创建，编辑，删除对象</li>
<li>可以批量选择对象并进行删除</li>
<li>拥有表单验证</li>
<li>快速反馈给用户的<em>flash</em>信息提示</li>
</ul>
<h2></h2>
<h2>批量操作（Batch Actions）</h2>
<p>批量操作是对一个被选择的模型对象集合（所有的对象或者只是它们中的一个子集）进行的操作。我们能够方便地为列表页面添加自定义的批量操作。<em>delete</em>操作默认就允许我们一次删除多个实体。</p>
<p>为了添加新的批量操作，我们需要重写<em>Admin</em>类的<em>getBatchActions()</em>方法。我们来添加一个批量处理<em>extend</em>的操作：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Admin/JobAdmin.php
// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this-&gt;hasRoute('edit') &amp;&amp; $this-&gt;isGranted('EDIT') &amp;&amp; $this-&gt;hasRoute('delete') &amp;&amp; $this-&gt;isGranted('DELETE')) {
        $actions['extend'] = array(
            'label'            =&gt; 'Extend',
            'ask_confirmation' =&gt; true // If true, a confirmation will be asked before performing the action
        );

    }

    return $actions;
}</pre><p><em>extend</em>批量操作的核心逻辑在<em>JobAdminController::batchActionExtend()</em>方法中。<em>batchActionExtend()</em>方法有一个查询参数，通过这个查询参数我们可以检索出被选择的模型对象。如果出于某种原因，你可能不需要通过默认的选择方法（selection method）来进行批量操作（例如，你可以选择在模板级别上进行细粒度的模型选择），那么可以传递一个值为null的查询参数。</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/JobAdminController.php
namespace Ibw\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery as ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class JobAdminController extends Controller
{
    public function batchActionExtend(ProxyQueryInterface $selectedModelQuery)
    {
        if ($this-&gt;admin-&gt;isGranted('EDIT') === false || $this-&gt;admin-&gt;isGranted('DELETE') === false) {
            throw new AccessDeniedException();
        }

        $modelManager = $this-&gt;admin-&gt;getModelManager();

        $selectedModels = $selectedModelQuery-&gt;execute();

        try {
            foreach ($selectedModels as $selectedModel) {
                $selectedModel-&gt;extend();
                $modelManager-&gt;update($selectedModel);
            }
        } catch (\Exception $e) {
            $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('sonata_flash_error', $e-&gt;getMessage());

            return new RedirectResponse($this-&gt;admin-&gt;generateUrl('list',$this-&gt;admin-&gt;getFilterParameters()));
        }

        $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('sonata_flash_success',  sprintf('The selected jobs validity has been extended until %s.', date('m/d/Y', time() + 86400 * 30)));

        return new RedirectResponse($this-&gt;admin-&gt;generateUrl('list',$this-&gt;admin-&gt;getFilterParameters()));
    }
}</pre><p>我们再来添加一个批量删除所有在60天内仍未被激活的<em>Job</em>数据的操作。对于这个操作，我们不需要在列表中选择任何的<em>Job</em>数据，因为在这个操作的逻辑中会去检索符合条件的记录并删除它们。</p><pre class="crayon-plain-tag">//src/Ibw/JobeetBundle/Admin/JobAdmin.php
// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this-&gt;hasRoute('edit') &amp;&amp; $this-&gt;isGranted('EDIT') &amp;&amp; $this-&gt;hasRoute('delete') &amp;&amp; $this-&gt;isGranted('DELETE')){
        $actions['extend'] = array(
            'label'            =&gt; 'Extend',
            'ask_confirmation' =&gt; true // If true, a confirmation will be asked before performing the action
        );

        $actions['deleteNeverActivated'] = array(
            'label'            =&gt; 'Delete never activated jobs',
            'ask_confirmation' =&gt; true // If true, a confirmation will be asked before performing the action
        );
    }

    return $actions;
}</pre><p>除了创建<em>batchActionDeleteNeverActivated</em>操作外，我们还会创建一个<em>JobAdminController:: batchActionDeleteNeverActivatedIsRelevant()</em>方法，这个方法需要得到确认之后才能够执行，以确保用户真的是要进行这个操作（在我们的这个例子中它总是返回true，因为选择需要被删除的<em>Job</em>数据的逻辑在<em>JobRepository::cleanup()</em>方法中）。</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/JobAdminController.php
// ...

public function batchActionDeleteNeverActivatedIsRelevant()
{
    return true;
}

public function batchActionDeleteNeverActivated()
{
    if ($this-&gt;admin-&gt;isGranted('EDIT') === false || $this-&gt;admin-&gt;isGranted('DELETE') === false) {
        throw new AccessDeniedException();
    }

    $em = $this-&gt;getDoctrine()-&gt;getManager();
    $nb = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;cleanup(60);

    if ($nb) {
        $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('sonata_flash_success',  sprintf('%d never activated jobs have been deleted successfully.', $nb));
    } else {
        $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('sonata_flash_info',  'No job to delete.');
    }

    return new RedirectResponse($this-&gt;admin-&gt;generateUrl('list',$this-&gt;admin-&gt;getFilterParameters()));
}</pre><p>今天我们就先到这了！明天我们来看看怎么样为管理员部分添加用户名（username）和密码（password），同时也会讨论<em>Symfony</em>的安全机制。</p>
<p>&nbsp;</p>
<p><span style="color: #ff0000">原文链接：<a style="color: #ff0000" href="http://www.intelligentbee.com/blog/2013/08/18/symfony2-jobeet-day-12-sonata-admin-bundle/">http://www.intelligentbee.com/blog/2013/08/18/symfony2-jobeet-day-12-sonata-admin-bundle/</a></span></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/374">jobeet第十二天:后台管理工具包-Sonata Admin</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/374/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
