redgem/servicesio-bundle

ServicesIO是一个提供轻松构建服务提供者方式的Symfony组件

安装: 955

依赖者: 0

建议者: 0

安全性: 0

星标: 0

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle

1.1.0 2020-03-21 17:56 UTC

This package is auto-updated.

Last update: 2024-09-22 04:24:31 UTC


README

ServicesIOBundle是一个提供轻松构建服务提供者方式的Symfony组件。

ServicesIO主要引入了两个组件

  • 一个读取器。读取器为您构建一个包装输入数据的模型。

    • 来自标准输入(例如请求对象)。
    • 来自您想要请求的第三方服务。
  • 一个视图层,用于构建响应的对象树视图。视图层由您的控制器调用,构建您想要渲染的树,并实际渲染它(目前仅支持JSON)。

安装组件

首先,在您的项目中添加并启用ServicesIOBundle

在composer中添加它

composer require redgem/servicesio-bundle:1.0.*

设置完成。

ServicesIO模型

该模型将帮助您读取和解码传递给您的控制器的树结构(例如json),例如。

模型组件的文档将在以后提供

ServicesIO视图

您可能已经习惯了在项目中使用Twig来构建和渲染HTML视图。

ServicesIO的目标是能够为数据树提供与Twig为HTML提供的一样强大和高效的东西。

概述

ServicesIO视图提供您创建的能力

  • 可重用的视图组件,可通过路径配置调用。
  • 可扩展的视图组件,具有数据占位符和填充它们的智能方式。
  • 与组件扩展的集成,可以通过扩展组件来覆盖视图组件。

在开始之前,您需要了解以下信息

  • 视图渲染系统通过两步系统运行

    1 - 您将在您的视图中使用语言无关的数据结构创建一个树,通过创建连接的节点。一个节点由3个可用的元素组成

    • 一个集合,用于处理其他节点的列表。
    • 一个项,用于处理带有节点附加的命名字段。
    • 一个标量,将是要渲染的数据片段(例如stringintfloat)。

    2 - 树的渲染。一旦完全构建,我们就可以将其转换为数据语言(目前仅支持json)并发送到输出。

基本操作:创建您的视图

现在,让我们用一个简短的例子来看看它是如何工作的。

为了便于阅读,我将删除与我们的主题无关的所有代码。

让我们创建一个小的示例项目:2个实体和2个控制器

实体Doctrine

class User
{
  /**
   * @MongoDB\Id
   */
  public $id;

  /**
   * @MongoDB\Field(type="string")
   */
  public $name;
}
class Message
{
  /**
   * @MongoDB\Id
   */
  public $id;

  /**
   * @MongoDB\Field(type="string")
   */
  public $title;
	
  /**
   * @MongoDB\Field(type="string")
   */
  public $description;
	
  /**
   * @MongoDB\Field(type="id")
   */
  public $user;
}

假设我有2个用户:authorvisitor,以及3条消息:message1、message2、message3。

以下是控制器(不包括它们的返回调用)

class MessageController
{
  public function singleAction(int $id, DocumentManager $documentManager)
  {
    $single = $documentManager
      ->getRepository(Message::class)->find($id);
  }

  public function listingAction(DocumentManager $documentManager)
  {
    $listing = $documentManager
      ->getRepository(Message::class)->findAll();
  }
}

基于此,我们现在需要创建装饰器和实体的视图元素,并从控制器中调用渲染。

让我们先完成messageAction的视图,使用一个ServicesIO视图类。

一个基本的View类必须

  • 扩展Redgem\ServicesIOBundle\Lib\View\View
  • 位置由您自己决定,我们建议使用<APP_OR_YOUR_BUNDLE>\View命名空间(因此在<APP_OR_YOUR_BUNDLE>/View目录中),以便进行清晰的组织。
  • 实现content(),这是构建视图树的场所。
  • 类名由您自己决定,我们建议将其命名为<NAME>View
namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class SingleView extends View
{
  public function content()
  {
    return $this->createItem()
        ->set('message', $this->createItem()
        ->set('id', $this->params['single']->id)
        ->set('title', $this->params['single']->title);
        ->set('description', $this->params['single']->description)
      );
  }
}

我们在这里创建一个简单的树,其中包含一个包含3个子项的Item对象idtitledescription,以显示我们的3个相应的message字段。

最后,让我们调用并渲染控制器

ServicesIOBundle中要调用的渲染服务名为Redgem\ServicesIOBundle\Lib\View\Service

调用需要两个参数

  • viewpath,视图类名。
  • 要发送的参数数组。
use Redgem\ServicesIOBundle\Lib\View\Service as View;

class MessageController
{
  public function singleAction(int $id, DocumentManager $documentManager, View $view)
  {
    $single = $documentManager
      ->getRepository(Message::class)->find($id);

    return $view->render(
      SingleView::class,
      ['single' => $single]
    );
  }
}

在控制器中作为参数发送的single变量在视图类中可通过$this->params['single']访问。

以第一个消息为例来调用它!

{
	"message" : {
	  "id" : "1",
	  "title" : "message 1 title",
	  "description" : "description 1 title"
	}
}

很好,现在我们有MessageView类树的json表示了!

现在我们可以将其复制到listing操作中。

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class ListingView extends View
{
  public function content()
  {
    $collection = $this->createCollection();
    
    foreach($this->params['listing'] as $message) {

      $collection->push(
        $this->createItem()
          ->set('id', $this->params['message']->id)
          ->set('title', $this->params['message']->title);
          ->set('description', $this->params['message']->description)
        );
      );
    }

    return $this->createItem()
      ->set('listing', $collection);
  }
}
use Redgem\ServicesIOBundle\Lib\View\Service as View;

class MessageController
{
  public function listingAction(DocumentManager $documentManager, View $view)
  {
    $listing = $documentManager
      ->getRepository(Message::class)->findAll();

    return $view->render(
      ListingView::class,
      ['listing' => $listing]
    );
  }
}

让我们调用它。

{
  "listing" : [
	  {
	    "id" : "1",
	    "title" : "message 1 title",
	    "description" : "description 1 title"
	  },
	  {
	    "id" : "2",
	    "title" : "message 2 title",
	    "description" : "description 2 title"
	  },
	  {
	    "id" : "3",
	    "title" : "message 3 title",
	    "description" : "description 3 title"
	  }
  ]
}

太棒了!

部分视图和片段控制器

我们遇到了一个问题。我们在两个类之间重复了创建message部分视图的操作。

幸运的是,有一个解决方案:创建一个可重用元素。

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class MessageView extends View
{
  public function content()
  {
    return $this->createItem()
      ->set('id', $this->params['message']->id)
      ->set('title', $this->params['message']->title);
      ->set('description', $this->params['message']->description)
    );
  }
}

正如你所见,一个可重用元素完全是一个普通的视图类。这意味着,如果你想的话,你可以直接用它来渲染控制器。

最后,我们在SingleViewListingView中使用它。

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class SingleView extends View
{
  public function content()
  {
    return $this->createItem()
      ->set('message', $this->partial(MessageView::class, ['message' => $this->params['single']]));
  }
}
namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class ListingView extends View
{
  public function content()
  {
    $collection = $this->createCollection();
    
    foreach($this->params['listing'] as $message) {

      $collection->push(
        $this->partial(MessageView::class, ['message' => $message])
      );
    }

    return $this->createItem()
      ->set('listing', $collection);
  }
}

当调用partial()方法从那里获取子树时,它将为你传递当前上下文(即从控制器发送的参数)与在第二个方法参数中添加的参数合并后的参数。

当然,最终的json渲染仍然是完全相同的。

现在让我们在所有地方用新的UserView丰富数据响应。

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class UserView extends View
{
  public function content()
  {
    return $this->createItem()
      ->set('id', $this->params['user']->id)
      ->set('name', $this->params['user']->name);
    );
  }
}

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class MessageView extends View
{
  public function content()
  {
    return $this->createItem()
      ->set('id', $this->params['message']->id)
      ->set('title', $this->params['message']->title);
      ->set('description', $this->params['message']->description)
      ->set('user', ($this->params['message']->user == null) ? null
      	: $this->partial(UserView:class, ['user' => $this->params['message']])
      )
    );
  }
}

现在单个请求将显示

{
  "message" : {
    "id" : "1",
    "title" : "message 1 title",
    "description" : "description 1 title",
    "user": {
      "id": "1",
      "name": "author"
    }
  }
}

除了partial()方法之外,还有一个controller()方法可用。它不仅调用视图,还会调用整个Symfony控制器作为片段

它的原型是

function controller($controller, $params = array())

其中

  • $controller:一个常规的Symfony控制器名称(作为字符串,带有完整类名)
  • $params:一个数组,它将与当前参数上下文合并,并传递给新的控制器。

将以这种方式处理响应

  • 如果这个新的控制器返回一个ServicesIO视图响应,片段树将在主树的正确位置合并。
  • 否则,响应将被处理为一个字符串,并在主树的正确位置合并。

还有一件事

  • get($service) - 字符串 $service:一个Symfony服务名称。
  • getParameter($parameter) - 字符串 $parameter:一个参数名称。

这些方法也是可用的。它们只是使用container调用一个Symfony服务或一个参数

视图扩展

现在我们想在响应的顶部装饰连接的用户。使用这个controller()方法来做这件事很简单。

我假设用户已经被Security组件正确认证。

public function visitorAction(View $view)
{
  return $view->render(
    UserView::class,
    ['user' => $this->getUser()] //returns a User object
  );
}

我将它添加到我的视图中

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class SingleView extends View
{
  public function content()
  {
    return $this->createItem()
      ->set('visitor', $this->controller('App\MyController\VisitorAction'))
      ->set('response', $this->createItem()
        ->set('message', $this->partial(MessageView::class, ['message' => $this->params['single']]))
      );
  }
}
namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class ListingView extends View
{
  public function content()
  {
    $collection = $this->createCollection();
    
    foreach($this->params['listing'] as $message) {
      $collection->push(
        $this->partial(MessageView::class, ['message' => $message])
      );
    }

    return $this->createItem()
      ->set('visitor', $this->controller('App\MyController\VisitorAction'))
      ->set('response', $this->createItem()
        ->set('listing', $collection)
      );
  }
}

现在单个请求将显示

{
  "visitor": {
    "id": "2",
    "name": "visitor"
  },
  "content": {
	  "message" : {
	    "id" : "1",
	    "title" : "message 1 title",
	    "description" : "description 1 title",
	    "user": {
	      "id": "1",
	      "name": "author"
	    }
	  }
	}
}

我的重复问题又出现了...在全局装饰器上。我用visitorresponse创建了两次主要对象。

我们可以通过改变我们的思维方式来解决这个问题。而不是只有一个类来构建树,让我们将其分成两个元素

  • 一个类来构建主要装饰节点(即,用visitorresponse
  • 填充每个视图类中response节点的字段。

首先,让我们创建装饰器。它仍然是一个普通的View

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class DecoratorView extends View
{
  public function content()
  {
    return $this->createItem()
    	->set('visitor', $this->controller('App\MyController\VisitorAction'))
    	->set('response', null, 'fullResponse');
  }
}

set()方法上有一个新的第三个参数。这个第三个参数是一个字符串,用于设置占位符选项的名称。占位符是树上的一个条目,以后可以被另一个值替换。

如果使用占位符,第二个值(此处为null)是默认值,如果占位符未填充,则将显示此默认值。

现在我们可以将我们的SingleViewListingView转换为使用此装饰器

namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class SingleView extends View
{
  public function getParent()
  {
    return DecoratorView::class;
  }

  public function blockFullResponse()
  {
    return $this->createItem()
      ->set('message', $this->partial(MessageView::class, ['message' => $this->params['single']]));
  }
}
namespace MyBundle\View;

use Redgem\ServicesIOBundle\Lib\View\View;

class ListingView extends View
{
  public function getParent()
  {
    return DecoratorView::class;
  }

  public function blockFullResponse()
  {
    $collection = $this->createCollection();
    
    foreach($this->params['listing'] as $message) {
      $collection->push(
        $this->partial(MessageView::class, ['message' => $message])
      );
    }

    return $this->createItem()
      ->set('listing', $collection);
  }
}

我们可以看到两个区别

  • 出现了一个新的getParent()方法:这意味着根节点将在这个视图类中移动。所有上下文都传递给此对象,显然,这是一个标准视图类,您可以在任何地方重用它。
  • content()方法被blockFullResponse()替换。content()方法是创建树根的方法。因此,它与getParent()方法不兼容。具有getParent()方法的类将仅填充其上定义的占位符。这就是blockXXX()方法的目的,其中XXX是占位符名称的驼峰式命名。方法(即这里的blockFullResponse)。

当然,您可以使用getParent()链多少层您想要的层次结构,并在其中定义所有占位符。

getParent()通常返回一个字符串。它也可以返回一个数组

public function getParent()
{
  return array(MyFriendBundleView::class, MyBundleDecoratorView::class);
}

在这种情况下,将选择实际实现的第一视图类。

为什么有这么多花哨的东西?

为什么这样做?您可能会说,使用常规的PHP类的extends关键字会更简单,避免使用viewpath,您是对的。但是,ServicesIO视图提供了一个简单、灵活和清晰的构建一些视图可重用组件的方法。

所以最后,我们可以调用我们的控制器

{
  "visitor": {
    "id": "2",
    "name": "visitor"
  },
  "content": {
	  "message" : {
	    "id" : "1",
	    "title" : "message 1 title",
	    "description" : "description 1 title",
	    "user": {
	      "id": "1",
	      "name": "author"
	    }
	  }
	}
}
{
  "visitor": {
    "id": "2",
    "name": "visitor"
  },
  "content": {
    "listing" : [
      {
        "id" : "1",
        "title" : "message 1 title",
        "description" : "description 1 title",
        "user": {
          "id": "1",
          "name": "author"
        }
      },
      {
        "id" : "2",
        "title" : "message 2 title",
        "description" : "description 2 title",
        "user": {
          "id": "1",
          "name": "author"
        }
      },
      {
        "id" : "3",
        "title" : "message 3 title",
        "description" : "description 3 title",
        "user": {
          "id": "1",
          "name": "author"
        }
      }
    ] 
  }
}