qafoolabs / no-framework-bundle
为 Symfony 应用提供各种改进
Requires (Dev)
- php: ~7.2
- phake/phake: *
- phpunit/phpunit: ^8.0
- sensio/framework-extra-bundle: ~3.0
- symfony/form: ^4.0
- symfony/framework-bundle: ^4.0
- symfony/security: ^4.0
- symfony/translation: ^4.0
- symfony/twig-bridge: ^4.0
This package is auto-updated.
Last update: 2021-01-27 22:14:59 UTC
README
免责声明:这不是 Qafoo 的官方产品,而是一个原型。我们不在此存储库上提供支持。
此捆绑包以该名称已停用,并已移至 https://github.com/gyro-project/mvc-bundle
目标
我们希望实现注册为服务的瘦控制器。任何控制器所需的服务的数量应该非常小(2-4个)。我们相信上下文应该明确传递给控制器,以避免将其隐藏在服务中。
最终,这应该使得控制器可以通过轻量级的单元测试和集成测试进行测试。通过构建从一开始就不依赖于 Symfony 的控制器,可以避免从 Symfony 中详细分离业务逻辑(可能除了 Request/Response 类)。
因此,此捆绑包提供了以下功能
- 从控制器返回视图数据
- 返回 RedirectRoute
- 控制器作为服务的辅助器
- 将异常从域/库类型转换为框架类型
安装
将捆绑包添加到您的应用程序内核
$bundles = array( // ... new QafooLabs\Bundle\NoFrameworkBundle\QafooLabsNoFrameworkBundle(), );
如果您使用 SensioFrameworkExtraBundle,请禁用视图监听器(现在不是必需的)
# app/config/config.yml sensio_framework_extra: view: annotations: false
从控制器返回视图数据
返回数组
此捆绑包替换了 Sensio FrameworkExtraBundle 中的 @Extra\Template()
注解支持,无需将注解添加到控制器操作中。
您可以直接从控制器返回数组,模板名称将根据控制器+操作-方法名称推断。
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; class DefaultController { public function helloAction($name = 'Fabien') { return array('name' => $name); } }
返回 TemplateView
有时返回数组从控制器返回不够灵活,出现两种使用案例
- 使用不同操作名称渲染模板。
- 向响应对象添加标题。
在这种情况下,您可以将前面的示例更改为返回 TemplateView
实例
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; use QafooLabs\MVC\TemplateView; class DefaultController { public function helloAction($name = 'Fabien') { return new TemplateView( array('name' => $name), 'hallo', // AcmeDemoBundle:Default:hallo.html.twig instead of hello.html.twig 201, array('X-Foo' => 'Bar') ); } }
注意:与默认 Symfony 基控制器上的 render()
方法不同,这里视图参数和模板名称是交换的。这是因为除了视图参数之外的所有内容都是可选的。
返回 ViewModel
通常控制器会快速收集与视图相关的逻辑,但由于这些数据转换方法的重要性不高,因此没有正确提取到 Twig 扩展中。这就是为什么除了返回数组支持之外,您还可以使用 ViewModel 并从您的操作中返回它们。
每个视图模型都是一个类,它映射到恰好一个模板,并且可以包含在Twig中以相同解析机制下可用的属性和方法,就像你返回数组一样,在view
模板名称下。
只要它没有扩展Symfony Response类,视图模型可以是任何类。
<?php # src/Acme/DemoBundle/View/Default/HelloView.php namespace Acme\DemoBundle\View\Default; class HelloView { public $name; public function __construct($name) { $this->name = $name; } public function getReversedName() { return strrev($this->name); } }
在你的控制器中,你只需返回视图模型
<?php # src/Acme/DemoBundle/Controller/HelloController.php namespace Acme\DemoBundle\Controller; class HelloController { public function helloAction($name) { return new HelloView($name); } }
它被渲染为AcmeBundle:Hello:hello.html.twig
,其中视图模型作为view
twig变量可用
Hello {{ view.name }} or {{ view.reversedName }}!
你可以选择扩展自QafooLabs\MVC\ViewStruct
。每个ViewStruct
实现都有一个构造函数,它接受并设置视图模型类上存在的键值对属性。
重定向路由
Symfony中的重定向更有可能发生在给定的路由内部。你可以从控制器中返回QafooLabs\MVC\RedirectRoute
,并且监听器会将它转换为正确的Symfony RedirectResponse
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; use QafooLabs\MVC\RedirectRoute; class DefaultController { public function redirectAction() { return new RedirectRoute('hello', array( 'name' => 'Fabien' )); } }
如果你想设置头或不同的状态码,你可以传递一个Response
作为第三个参数,它将用于创建新的响应。
添加Cookies、Flash消息、缓存头
当从控制器返回视图模型、数组或重定向路由时,如果没有直接访问响应,就没有简单的方法来添加响应头。这就是PHP生成器发挥作用的地方,你可以使用yield
额外的响应元数据。
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; use QafooLabs\MVC\Headers; use QafooLabs\MVC\Flash; use Symfony\Component\HttpFoundation\Cookie; class DefaultController { public function helloAction($name) { yield new Cookie('name', $name); yield new Headers(['X-Hello' => $name]); yield new Flash('warning', 'Hello ' . $name); return ['name' => $name]; } }
将TokenContext注入到操作中
Symfony中,通过security.context
服务可以访问安全相关信息。从设计角度来看,这是不好的,因为它在需要访问安全相关信息时引入了一个有状态的服务。
为了避免从服务中访问安全状态,它需要作为参数传递,从控制器操作开始。
这就是TokenContext
类的作用。只需为任何操作添加类型提示即可,NoFrameworkBundle将传递此对象到你的操作。你可以从中访问各种安全相关的方法。
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; use QafooLabs\MVC\TokenContext; class DefaultController { public function redirectAction(TokenContext $context) { if ($context->hasToken()) { $user = $context->getCurrentUser(); } else if ($context->hasAnonymousToken()) { // do anon stuff } if ($context->isGranted('ROLE_ADMIN')) { // do admin stuff echo $context->getCurrentUserId(); echo $context->getCurrentUsername(); } } }
Symfony使用具体的实现SymfonyTokenContext
用于使用security.context
的接口。
在单元测试中,如果你想测试控制器,可以使用MockTokenContext
代替。它不适用于复杂的isGranted()
检查或令牌,但如果只使用用户对象,它允许非常简单的测试设置。
使用FormRequest
在Symfony中处理表单通常会导致复杂、不可测试的控制器操作,这些操作与各种Symfony服务紧密耦合。为了避免在控制器内部处理form.factory
,我们引入了一个专门请求对象来隐藏所有这些。
<?php # src/Acme/DemoBundle/Controller/DefaultController.php namespace Acme\DemoBundle\Controller; use QafooLabs\MVC\FormRequest; use QafooLabs\MVC\RedirectRoute; class ProductController { private $repository; public function __construct(ProductRepository $repository) { $this->repository; } public function editAction(FormRequest $formRequest, $id) { $product = $this->repository->find($id); if (!$formRequest->handle(new ProductEditType(), $product)) { return array('form' => $formRequest->createFormView(), 'entity' => $product); } $product = $formRequest->getValidData(); $this->repository->save($product); return new RedirectRoute('Product.show', array('id' => $id)); } }
在测试中,你可以使用new QafooLabs\MVC\Form\InvalidFormRequest()
和new QafooLabs\MVC\Form\ValidFormRequest($validData)
来在控制器测试中使用表单。
会话的参数转换器
你可以将会话作为参数传递给控制器
public function indexAction(Session $session)
{
}
Flash消息的参数转换器
不再支持传递QafooLabs\MVC\Flash
。你必须迁移代码以使用yield new Flash($type, $message);
代替。
## Helper for Controllers as Service
We added a ``controller_utils`` service that offers the functionality
of the Symfony base controller plus some extras.
See my blog post [Extending Symfony2: Controller Utils](http://www.whitewashing.de/2013/06/27/extending_symfony2__controller_utilities.html)
for reasoning.
## Convert Exceptions
Usually the libraries you are using or your own code throw exceptions that can be turned
into HTTP errors other than the 500 server error. To prevent having to do this in the controller
over and over again you can configure to convert those exceptions in a listener:
qafoo_labs_no_framework:
convert_exceptions:
Doctrine\ORM\EntityNotFoundException: Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Doctrine\ORM\ORMException: 500
Notable facts about the conversion:
- Both Target Exception classes or just a HTTP StatusCode can be specified
- Subclasses are checked for as well.
- If you don't define conversions the listener is not registered.
- If an exception is converted the original exception will specifically logged
before conversion. That means when an exception occurs it will be logged
twice.
## Turbolinks Support
To improve performance with traditional HTML response webapplications Basecamp
introduced [Turbolinks](https://github.com/turbolinks/turbolinks), a library
that uses Ajax to follow same domain links and then replaces only head title
and body to keep javascript and CSS in place.
The QafooLabsNoFrameworkBundle provides out of the box support for the
turbolinks JS library in the browser by setting the `Turbolinks-Location`
header after redirects.