teqneers / ext-direct
一个基础组件,用于将 Sencha Ext JS Ext.direct 集成到 PHP 应用程序中
Requires
- php: 8.0.*|8.1.*
- ext-json: *
- doctrine/annotations: ~1.13
- jms/metadata: ~2.6
- jms/serializer: ~3.17
- symfony/dependency-injection: ~5.0
- symfony/event-dispatcher: ~5.0
- symfony/expression-language: ~5.0
- symfony/http-foundation: ~5.0
- symfony/security-core: ~5.0
- symfony/validator: ~5.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- symfony/phpunit-bridge: ~5.0
- symfony/stopwatch: ~5.0
README
一个基础组件,用于将 Sencha Ext JS Ext.direct 集成到 PHP 应用程序中
简介
此库为 Sencha Ext.direct 的服务器端实现提供支持,Ext.direct 是 Sencha 的 Ext JS 和 Sencha Touch 的一部分,是一种 RPC 风格的通信组件。
Ext Direct 是一种平台和语言无关的远程过程调用 (RPC) 协议。Ext Direct 允许 Ext JS 应用程序的客户端与任何符合该规范的服务器平台无缝通信。Ext Direct 是无状态的且轻量级,支持 API 发现、调用批处理和服务器到客户端事件等功能。
目前,此库仅用作 teqneers/ext-direct-bundle 的基础,这是一个将 *Ext.direct* 集成到基于 Symfony 的应用程序中的 Symfony 扩展包。我们尚未尝试将库作为独立的组件或在任何其他环境中(除了 Symfony 环境)使用,因此以下内容仅代表理论上的工作方式,而不涉及扩展包。我们欢迎任何帮助和贡献,以使库在扩展包之外更有用。
安装
您可以使用 composer 安装此库
composer require teqneers/ext-direct
或将包直接添加到您的 composer.json 文件中。
示例
命名策略决定了如何将 PHP 类名和命名空间转换为 JavaScript 兼容的 Ext.direct 操作名称。默认命名策略将 \
命名空间分隔符转换为 .
。因此,My\Namespace\Service
转换为 My.namespace.Service
。请注意,转换必须是可逆的(My.namespace.Service
=> My\Namespace\Service
)。
$namingStrategy = new TQ\ExtDirect\Service\DefaultNamingStrategy();
服务注册表使用来自 jms/metadata
库的元数据工厂和相关注释驱动程序(该注释驱动程序反过来使用 doctrine/annotations
注释读取器)来读取有关可能注释的服务类的元信息。
$serviceRegistry = new TQ\ExtDirect\Service\DefaultServiceRegistry( new Metadata\MetadataFactory( new TQ\ExtDirect\Metadata\Driver\AnnotationDriver( new Doctrine\Common\Annotations\AnnotationReader() ) ), $namingStrategy );
服务注册表可以通过调用 addServices()
或 addService()
手动填充,或者通过使用 TQ\ExtDirect\Service\ServiceLoader
导入服务。默认实现 \TQ\ExtDirect\Service\PathServiceLoader
可以从一组给定的路径中读取类。
事件调度器是可选的,但使用功能如参数转换和验证、结果转换的配置文件监听器时是必需的。
$eventDispatcher = new Symfony\Component\EventDispatcher\EventDispatcher();
路由器用于将传入的 Ext.direct 请求转换为对正确服务类的 PHP 方法调用。`ContainerServiceFactory` 支持从 Symfony 依赖注入容器中检索服务或实例化没有任何构造函数参数的简单服务。静态服务调用绕过服务工厂。
$router = new TQ\ExtDirect\Router\Router( new TQ\ExtDirect\Router\ServiceResolver( $serviceRegistry, new TQ\ExtDirect\Service\ContainerServiceFactory( /* a Symfony\Component\DependencyInjection\ContainerInterface */ ) ), $eventDispatcher );
端点对象是所有 Ext.direct 服务器端组件的前端。使用其 createServiceDescription()
方法,可以获取符合标准的 API 描述,而 handleRequest()
接受一个 Symfony\Component\HttpFoundation\Request
并返回一个 Symfony\Component\HttpFoundation\Response
,其中包含接收到的服务调用所对应的 Ext.direct 响应。
$endpoint = TQ\ExtDirect\Service\Endpoint( 'default', // endpoint id new TQ\ExtDirect\Description\ServiceDescriptionFactory( $serviceRegistry, 'My.api', $router, new TQ\ExtDirect\Router\RequestFactory(), 'My.api.REMOTING_API' ) );
端点管理器只是一个简单的端点集合,它允许使用端点 ID 进行检索。这允许轻松公开多个独立的 API。
$manager = new TQ\ExtDirect\Service\EndpointManager(); $manager->addEndpoint($endpoint); $defaultEndpoint = $manager->getEndpoint('default'); $apiResponse = $defaultEndpoint->createServiceDescription('/path/to/router'); $apiResponse->send(); $request = Symfony\Component\HttpFoundation\Request::createFromGlobals(); $response = $defaultEndpoint->handleRequest($request); $response->send();
可以通过在传递给路由器的事件调度器上使用事件监听器来操纵和增强路由过程。该库提供了四个事件订阅者,允许
- 在调用服务方法之前转换参数
- 在调用服务方法之前验证参数
- 在将结果发送回客户端之前转换服务方法调用结果
- 对路由器进行仪表化以获取计时信息(用于增强Symfony分析器时间线)
提供的参数和结果转换器使用jms/serializer
库提供扩展的(反)序列化功能,而默认的参数验证器则使用symfony/validator
库。
$eventDispatcher->addSubscriber( new TQ\ExtDirect\Router\EventListener\ArgumentConversionListener( new TQ\ExtDirect\Router\ArgumentConverter(/* a JMS\Serializer\Serializer */) ) ); $eventDispatcher->addSubscriber( new TQ\ExtDirect\Router\EventListener\ArgumentValidationListener( new TQ\ExtDirect\Router\ArgumentValidator(/* a Symfony\Component\Validator\Validator\ValidatorInterface */) ) ); $eventDispatcher->addSubscriber( new TQ\ExtDirect\Router\EventListener\ResultConversionListener( new TQ\ExtDirect\Router\ResultConverter(/* a JMS\Serializer\Serializer */) ) ); $eventDispatcher->addSubscriber( new TQ\ExtDirect\Router\EventListener\StopwatchListener( /* a Symfony\Component\Stopwatch\Stopwatch */ ) );
服务注解
要通过Ext.direct API公开的服务必须装饰适当的元信息。目前这只能通过注解(如从Doctrine、Symfony或其他现代PHP库中已知)来实现。
每个将被公开为Ext.direct操作的服务类都必须使用TQ\ExtDirect\Annotation\Action
进行注解。对于既不是静态的也不能用无参构造函数实例化的服务,Action
注解可以可选地接受一个服务id参数。
use TQ\ExtDirect\Annotation as Direct; /** * @Direct\Action() */ class Service1 { // service will be instantiated using the parameter-less constructor if called method is not static } /** * @Direct\Action("app.direct.service2") */ class Service2 { // service will be retrieved from the dependency injection container using id "app.direct.service2" if called method is not static }
此外,每个将被公开在Ext.direct操作上的方法都必须使用TQ\ExtDirect\Annotation\Method
进行注解。该Method
注解可以可选地接受true
以指定该方法为表单处理器(接受常规表单提交)或false
以指定该方法为常规的Ext.direct方法(这是默认值)。
/** * @Direct\Action("app.direct.service3") */ class Service3 { /** * @Direct\Method() */ public function methodA() { // regular method } /** * @Direct\Method(true) */ public function methodB() { // form handler method } }
在Ext.direct规范中描述的扩展功能,如命名参数和严格命名参数,目前无法通过注解系统公开。
可以注解通过Ext.direct请求调用的方法中的参数,以应用参数验证。这需要将TQ\ExtDirect\Router\EventListener\ArgumentValidationListener
注册到相应的事件调度器。
use Symfony\Component\Validator\Constraints as Assert; /** * @Direct\Action("app.direct.service4") */ class Service4 { /** * @Direct\Method() * @Direct\Parameter("a", { @Assert\NotNull(), @Assert\Type("int") }) * * @param int $a */ public function methodA($a) { } }
如果被调用的方法的签名暴露了具有对Symfony\Component\HttpFoundation\Request
和/或TQ\ExtDirect\Router\Request
类型提示的参数,则自动将传入的Symfony HTTP请求和/或原始的Ext.direct请求注入到方法调用中。这对于表单处理方法尤为重要,因为除此之外没有其他方式可以访问传入的HTTP请求参数(表单提交)。
一旦启用TQ\ExtDirect\Router\EventListener\ArgumentConversionListener
,就可以在服务方法中使用严格类型的对象参数。这些参数将从传入的JSON请求中自动反序列化,并注入到方法调用中。
对于从服务方法调用中返回的对象也是如此。如果启用TQ\ExtDirect\Router\EventListener\ResultConversionListener
,则返回值将自动序列化为JSON,即使它们是非平凡的对象也是如此。
参数以及返回值的转换都基于Johannes Schmitt的优秀库jms/serializer
。有关更多信息,请参阅文档。
规范
Ext Direct规范可以在Sencha的文档网站上找到。
许可证
MIT许可证(MIT)
版权所有(c)2015 TEQneers GmbH & Co. KG
特此授予任何获得本软件及其相关文档副本(“软件”)的人免费使用该软件的权利,不受限制,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本的权利,并允许将软件提供给获得软件的人,但受以下条件约束
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
软件按“现状”提供,不提供任何形式的保证,无论是明示的、默示的、还是关于适销性、特定用途适用性或非侵权的保证。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任承担责任,无论该责任是因合同行为、侵权行为或其他任何方式引起的,不论该责任是否与软件有关、使用软件或以其他方式参与软件。