表单视图
在上一章节中,你已经看到了冰山一角。但是还有很多东西值得探索!在接下来的章节中,你将为更复杂的 BlogPost
模型创建一个 Admin 类。同时,你将学习如何使事情变得更漂亮一些。
引导 Admin 类
基本类定义将与 CategoryAdmin
相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Admin/BlogPostAdmin.php
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
final class BlogPostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form): void
{
// ... configure $form
}
protected function configureListFields(ListMapper $list): void
{
// ... configure $list
}
}
服务定义也同样适用
1 2 3 4 5 6 7
# config/services.yaml
services:
admin.blog_post:
class: App\Admin\BlogPostAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\BlogPost, manager_type: orm, label: 'Blog post' }
配置 FormMapper
如果你已经了解 Symfony 表单组件,那么 FormMapper
看起来会非常相似。
你使用 add()
方法将字段添加到表单中。第一个参数是字段值映射到的属性名称,第二个参数是字段的类型(请参阅 字段类型参考),第三个参数是用于自定义表单类型的其他选项。只有第一个参数是必需的,因为表单组件具有类型猜测器来猜测类型。
BlogPost
模型有 4 个属性:id
、title
、body
、category
。id
属性的值由数据库自动生成。这意味着表单视图需要 3 个字段:title、body 和 category。
title 和 body 字段是 TextType
和 TextareaType
字段,你可以直接添加它们
1 2 3 4 5 6 7 8 9 10 11 12
// src/Admin/BlogPostAdmin.php
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
protected function configureFormFields(FormMapper $form): void
{
$form
->add('title', TextType::class)
->add('body', TextareaType::class)
;
}
但是,category 字段将引用另一个模型。你如何解决这个问题呢?
添加引用其他模型的字段
使用实体类型
关于如何添加引用其他模型的字段,你有几种不同的选择。最基本的选择是使用 Doctrine Bridge 提供的 EntityType
。这将渲染一个选择字段,并将可用的实体作为选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Admin/BlogPostAdmin.php
use App\Entity\Category;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
protected function configureFormFields(FormMapper $form): void
{
$form
// ...
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
])
;
}
由于每篇博客文章只有一个类别,因此它呈现为选择列表

当管理员想要创建一个新类别时,他们需要转到类别管理页面并创建一个新类别。
使用 Sonata 模型类型
为了让管理员的生活更轻松,你可以使用 ModelType 字段。此字段类型也将呈现为选择字段,但它包含一个创建按钮,用于打开一个对话框,其中包含被引用模型的管理员界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Admin/BlogPostAdmin.php
use App\Entity\Category;
use Sonata\AdminBundle\Form\Type\ModelType;
protected function configureFormFields(FormMapper $form): void
{
$form
->add('category', ModelType::class, [
'class' => Category::class,
'property' => 'name',
])
;
}

使用分组和标签页
使用分组
目前,所有内容都放在一个块中。由于表单只有三个字段,因此仍然可用,但它很快就会变得非常混乱。为了解决这个问题,form mapper 还支持将字段分组在一起。
例如,title 和 body 字段可以属于 Content 组,而 category 字段可以属于 Meta data 组。为此,请使用 with()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// src/Admin/BlogPostAdmin.php
use App\Entity\Category;
use Sonata\AdminBundle\Form\Type\ModelType
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
protected function configureFormFields(FormMapper $form): void
{
$form
->with('Content')
->add('title', TextType::class)
->add('body', TextareaType::class)
->end()
->with('Meta data')
->add('category', ModelType::class, [
'class' => Category::class,
'property' => 'name',
])
->end()
;
}
第一个参数是组的名称/标签,第二个参数是选项数组。例如,你可以将 HTML 类传递给组以调整样式
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Admin/BlogPostAdmin.php
protected function configureFormFields(FormMapper $form): void
{
$form
->with('Content', ['class' => 'col-md-9'])
// ...
->end()
->with('Meta data', ['class' => 'col-md-3'])
// ...
->end()
;
}
这将产生一个更漂亮的编辑页面

使用标签页
如果你有更多的选项,你还可以使用多个标签页,使用 tab()
快捷方法
1 2 3 4 5 6 7 8 9 10 11 12
$form
->tab('Post')
->with('Content', ...)
// ...
->end()
// ...
->end()
->tab('Publish Options')
// ...
->end()
;
创建一个博客文章
你现在已经完成了 BlogPost
模型的漂亮的表单视图。现在是时候通过创建一个帖子来测试它了。
按下 “创建” 按钮后,你可能会看到一条绿色消息,例如:项目 “AppEntityBlogPost:00000000192ba93c000000001b786396” 已成功创建。
虽然 SonataAdminBundle 非常友好地通知管理员创建成功,但类名和某种哈希值并不是很友好。这是 SonataAdminBundle 中对象的默认字符串表示形式。你可以通过在 Admin 类中定义 toString()
方法来更改它。这将接收要转换为字符串的对象作为第一个参数
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Admin/BlogPostAdmin.php
use App\Entity\BlogPost;
final class BlogPostAdmin extends AbstractAdmin
{
public function toString(object $object): string
{
return $object instanceof BlogPost
? $object->getTitle()
: 'Blog Post'; // shown in the breadcrumb on the create view
}
}
注意
没有下划线前缀!toString()
是正确的!