qafoolabs/framework-extra-bundle

此包已废弃,不再维护。没有建议的替代包。

为Symfony应用程序提供各种改进

安装: 13,981

依赖者: 0

建议者: 0

安全: 0

星标: 127

关注者: 10

分支: 12

开放问题: 9

类型:symfony-bundle

v3.0-alpha2 2019-03-29 20:34 UTC

This package is auto-updated.

Last update: 2021-01-27 22:11:35 UTC


README

免责声明:这不是一个官方的Qafoo产品,而是一个原型。我们不为此存储库提供支持。

Build Status

此捆绑包以该名称已停止生产,并已移动到https://github.com/gyro-project/mvc-bundle

目标

我们希望实现轻量级的控制器,它们被注册为服务。任何控制器中所需的服务数量应该非常小(2-4)。我们相信上下文应该明确传递给控制器,以避免将其隐藏在服务中。

最终,这应该使得控制器可以通过轻量级的单元测试和集成测试进行测试。通过构建不依赖于Symfony的控制器(也许除了Request/Response类之外),可以无需详细地将Symfony与您的业务逻辑分开。

因此,此捆绑包提供以下功能

  • 从控制器返回视图数据
  • 返回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

有时会出现两种情况,从控制器返回数组不够灵活

  1. 使用不同的操作名称渲染模板。
  2. 向响应对象添加标题。

在这种情况下,您可以将前面的示例更改为返回一个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()方法不同,这里视图参数和模板名称是交换的。这是因为除了视图参数之外,一切都是可选的。

返回ViewModels

通常控制器会迅速收集与视图相关的逻辑,但由于这些数据转换方法的不重要性,这些逻辑并没有被正确地提取到Twig扩展中。这就是为什么除了返回数组支持之外,您还可以使用视图模型,并从您的操作中返回它们。

每个视图模型都是一个类,它映射到恰好一个模板,并可以包含属性+方法,这些属性和方法可以在使用相同的解析机制时在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) 来在控制器测试中使用表单。

Session 的参数转换器

你可以将会话作为参数传递给控制器。

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.