roadiz / abstract-api-theme
将 Roadiz 内容暴露为公共 REST API。
Requires
- php: >=7.4
- friendsofsymfony/jsrouting-bundle: ^2.5.3
- league/oauth2-server: ^8.2.2
- nyholm/psr7: ^1.3
- ramsey/uuid: ^3.8
- roadiz/roadiz: ~1.7.0
- roadiz/rozier: ~1.7.0
- symfony/psr-http-message-bridge: ^2.0
- symfony/rate-limiter: ^5.2
Requires (Dev)
- phpstan/phpstan: ^1.8.2
- phpstan/phpstan-doctrine: ^1.3.11
- squizlabs/php_codesniffer: ^3.3
- dev-master
- 4.0.18
- 4.0.17
- 4.0.16
- 4.0.15
- 4.0.14
- 4.0.13
- 4.0.12
- 4.0.11
- 4.0.10
- 4.0.9
- 4.0.8
- 4.0.7
- 4.0.6
- 4.0.5
- 4.0.4
- 4.0.3
- 4.0.2
- 4.0.1
- 4.0.0
- 3.1.x-dev
- 3.1.20
- 3.1.19
- 3.1.18
- 3.1.17
- 3.1.16
- 3.1.15
- 3.1.14
- 3.1.13
- 3.1.12
- 3.1.11
- 3.1.10
- 3.1.9
- 3.1.8
- 3.1.7
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- v3.0.x-dev
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- v2.x-dev
- v2.1.x-dev
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.2
- 2.0.1
- 2.0.0
- 1.1.0
- 1.0.0
- dev-develop
- dev-feature/tag_listing
This package is auto-updated.
Last update: 2024-09-07 16:27:03 UTC
README
将 Roadiz 内容暴露为公共 REST API。 主要用于 Roadiz Headless edition。
OAuth2 类和逻辑高度基于 trikoder/oauth2-bundle,该包实现了 thephpleague/oauth2-server 以集成到 Symfony 生态系统。
配置
使用 .env 文件
此中间件主题使用 symfony/dotenv
将 .env
变量导入到您的项目中。请确保创建一个至少包含以下配置的文件
JWT_PASSPHRASE=changeme # vendor/bin/generate-defuse-key DEFUSE_KEY=changeme
您的 Roadiz 入口点必须初始化 DotEnv
对象,以从 .env
文件或系统环境(例如您的 Docker 容器环境)中获取此配置。
注册 API 主题
- 将 API 基础服务添加到您的项目
app/AppKernel.php
# AppKernel.php /** * {@inheritdoc} */ public function register(\Pimple\Container $container) { parent::register($container); /* * Add your own service providers. */ $container->register(new \Themes\AbstractApiTheme\Services\AbstractApiServiceProvider()); }
或您的 config.yml
additionalServiceProviders: - \Themes\AbstractApiTheme\Services\AbstractApiServiceProvider
- 您不需要注册此抽象主题 来启用其路由或翻译
- 通过扩展
AbstractApiThemeApp
创建一个新的主题,其中包含您的 API 逻辑 - 或 在您继承自其他中间件主题的自定义主题应用中使用
AbstractApiThemeTrait
, - 并将 API 认证方案添加到 Roadiz 的 firewall-map...
选择简单的 API-Key 或完整的 OAuth2 认证方案
- API-key 方案旨在通过 Referer 正则表达式和 非过期 api-key 来控制您的 公共 API 使用。这是一种非常轻的保护措施,仅适用于浏览器,并且仅应与公共数据一起使用。
- OAuth2 方案将使用短暂的 访问令牌 通过身份验证和授权中间件来保护您的 API。
<?php declare(strict_types=1); namespace Themes\MyApiTheme; use Symfony\Component\HttpFoundation\RequestMatcher; use Pimple\Container; use Themes\AbstractApiTheme\AbstractApiThemeTrait; class MyApiThemeApp extends FrontendController { use AbstractApiThemeTrait; protected static $themeName = 'My API theme'; protected static $themeAuthor = 'REZO ZERO'; protected static $themeCopyright = 'REZO ZERO'; protected static $themeDir = 'MyApiTheme'; protected static $backendTheme = false; public static $priority = 10; /** * @inheritDoc */ public static function addDefaultFirewallEntry(Container $container) { /* * API MUST be the first request matcher */ $requestMatcher = new RequestMatcher( '^'.preg_quote($container['api.prefix']).'/'.preg_quote($container['api.version']) ); $container['accessMap']->add( $requestMatcher, [$container['api.base_role']] ); /* * Add default API firewall entry. */ $container['firewallMap']->add( $requestMatcher, // launch firewall rules for any request within /api/1.0 path [$container['api.firewall_listener']], $container['api.exception_listener'] // do not forget to add exception listener to enforce accessMap rules ); /* * OR add OAuth2 API firewall entry. */ // $container['firewallMap']->add( // $requestMatcher, // launch firewall rules for any request within /api/1.0 path // [$container['api.oauth2_firewall_listener']], // $container['api.exception_listener'] // do not forget to add exception listener to enforce accessMap rules // ); // Do not forget to register default frontend entries // AFTER API not to lose preview feature parent::addDefaultFirewallEntry($container); } }
- 创建新的角色
ROLE_ADMIN_API
和ROLE_API
以启用 API 访问和管理部分 - 更新您的数据库模式以添加
Applications
表。
bin/roadiz orm:schema-tool:update --dump-sql --force
为您的网站启用授权类型
如果您选择了 OAuth2 应用程序,在继续之前必须为授权服务器启用授权类型:只需如下扩展 AuthorizationServer::class
Roadiz 服务。
AbstractApiTheme 当前支持
client_credentials
授权authorization_code
授权(不包含 刷新令牌)
/* * Enable grant types */ $container->extend(AuthorizationServer::class, function (AuthorizationServer $server, Container $c) { // Enable the client credentials grant on the server $server->enableGrantType( new \League\OAuth2\Server\Grant\ClientCredentialsGrant(), new \DateInterval('PT1H') // access tokens will expire after 1 hour ); // Enable the authorization grant on the server $authCodeGrant = new \League\OAuth2\Server\Grant\AuthCodeGrant( $c[AuthCodeRepositoryInterface::class], $c[RefreshTokenRepositoryInterface::class], new \DateInterval('PT10M') // authorization_codes will expire after 10 min ); $server->enableGrantType( $authCodeGrant, new \DateInterval('PT3H') // access tokens will expire after 3 hours ); return $server; });
自定义 CORS
CORS 处理高度基于 nelmio/NelmioCorsBundle,选项仅作为您可以扩展以用于您网站的服务来处理。
这将自动拦截包含 Origin
标头的请求。预检请求必须使用 OPTIONS
动词执行,并且必须包含 Origin
和 Access-Control-Request-Method
标头。
/** * @return array */ $container['api.cors_options'] = [ 'allow_credentials' => true, 'allow_origin' => ['*'], 'allow_headers' => true, 'origin_regex' => false, 'allow_methods' => ['GET'], 'expose_headers' => ['link', 'etag'], 'max_age' => 60*60*24 ];
使用缓存标签
序列化上下文可以收集在请求期间找到的每个 nodes ID、documents ID 和 tags ID,称为 缓存标签。
// In your application/theme service provider $container['api.use_cache_tags'] = true;
缓存标签将被附加到响应 X-Cache-Tags
标头,并允许您更选择性地清除您的反向代理缓存。以下是缓存标签语法
n{node.id}
(例如:n98
)用于节点t{tag.id}
(例如:t32
)用于标签d{document.id}
(例如:d291
)用于文档
缓存标签语法是最短的,以避免在Nginx配置中达到最大头部大小限制。
创建新应用程序
应用程序保存您的API密钥,并使用正则表达式模式控制对Referer
的传入请求。
机密应用程序:OAuth2
保留角色/作用域
preview
作用域将转换为ROLE_BACKEND_USER
,这是访问未发布节点的必需角色名称。
通用 Roadiz API
API 路由列表
/api/1.0
入口点将列出所有可用路由
OAuth2 入口点
GET /authorize
用于授权码授予流程(第一部分)GET /token
用于授权码授予流程(第二部分)和client_credential
授予流程(仅部分)
有关授权码授予的更多详细信息,请参阅ThePHPLeague OAuth2 Server文档
授权码授予流程将非认证用户重定向到带有经典Roadiz登录表单的GET /oauth2-login
。您可以调用GET /authorize/logout
强制用户注销。请注意,授权码授予不会给予每个应用程序角色(除非登录用户之前已经有了它们)(除ROLE_SUPERADMIN
外)。用户将被要求授权应用程序角色的权限,但出于安全原因(权限升级),他将不会从中受益。确保在邀请用户使用您的OAuth2应用程序之前,您的用户拥有正确的角色。
用户详情入口点
/api/1.0/me
入口点将显示有关您的应用程序/用户的详细信息
列出节点源
/api/1.0/nodes-sources
:列出所有节点源,无论其类型如何。/api/1.0/{node-type-name}
:按类型列出节点源
如果您创建了一个Event
节点类型,API内容将在/api/1.0/event
端点提供。序列化上下文将自动在您的API资源中添加@id
、@type
、slug
和url
字段。
{ "hydra:member": [ { "slug": "home", "@type": "Page", "node": { "nodeName": "accueil", "tags": [] }, "title": "Accueil", "publishedAt": "2021-01-18T23:32:39+01:00", "@id": "http://example.test/dev.php/api/1.0/page/2/fr", "url": "/dev.php/home" } ], "hydra:totalItems": 1, "@id": "/api/1.0/page", "@type": "hydra:Collection", "hydra:view": { "@id": "/api/1.0/page", "@type": "hydra:PartialCollectionView" } }
注意:在列表上下文中,仅公开默认组中的节点类型字段。如果您想防止在列表期间序列化某些节点类型字段,可以给它们一个组名。这可以有助于避免文档或节点引用字段使您的JSON响应膨胀。
过滤器
- itemsPerPage:
int
- page:
int
- _locale:
string
如果未设置_locale,Roadiz将与现有的Accept-Language
头协商 - search:
string
- order:
array
示例:order[publishedAt]: DESC
,值包括ASC
DESC
- properties:
array
通过名称过滤序列化属性 - archive:
string
示例:archive: 2019-02
或archive: 2019
。此参数仅在publishedAt
字段上使用
在NodesSources
内容上
- path:
string
根据节点名称或别名对节点源进行过滤,例如:/home
。路径需要_locale
过滤器来获取正确的翻译。路径过滤器还可以解析任何重定向,如果它与有效的节点源相关联。 - id:
id
节点源ID - title:
string
- not:
array<int|string>|int|string
,通过它们的数字ID、节点名称或@id过滤出一个或多个节点 - publishedAt:
DateTime
或array
包含after
before
strictly_after
strictly_before
- tags:
array<string>
通过标签过滤(不能与search
一起使用) - tagExclusive:
bool
通过标签进行AND逻辑过滤(不能与search
一起使用) - node.parent:
int|string
数字ID、节点名称或@id - node.aNodes.nodeA:
int|string
(数字ID、节点名称或@id)通过节点引用进行过滤(查找被引用的节点) - node.bNodes.nodeB:
int|string
(数字ID、节点名称或@id)通过节点引用进行过滤(查找拥有引用的节点) - node.aNodes.field.name:
string
通过节点类型字段名称过滤节点引用(可选,如果没有设置,则将对任何节点引用应用node.aNodes.nodeA
过滤器) - node.bNodes.field.name:
string
通过节点类型字段名筛选节点引用(可选,未设置时,将对任何节点引用应用node.bNodes.nodeB
过滤) - node.visible:
bool
- node.home:
bool
- node.nodeType:
array|string
通过节点类型筛选节点源 - node.nodeType.reachable:
bool
以及任何已 索引 的日期、日期时间布尔节点类型字段
区域设置筛选
_locale
过滤 设置 Roadiz 主要翻译,用于所有数据库查找,确保始终将其设置为正确的区域设置,否则在针对法语查询使用 search
或 path
过滤时不会得到任何结果。
路径筛选
path
过滤 使用 Roadiz 内部路由器 仅搜索与您的查询匹配的结果。您可以使用
- 节点源规范路径,例如:
/about-us
- 节点源 nodeName 路径:例如:
/en/about-us
- 重定向路径,例如:
/old-about-us
如果您得到一个结果,您将在 hydra:member > 0 > url
字段中找到规范路径以在您的前端框架中创建重定向并宣传节点源的新 URL。
使用 Accept-Language 重定向主页路径
使用 path
过滤器与 /
值 仅,您可以将 Accept-Language
标头发送到 API 以让它决定对您的消费者最好的翻译。如果找到有效数据,API 将以包含接受的区域设置的 Content-Language
标头响应。要启用此行为,您必须启用 force_locale
Roadiz 设置以确保每个主页路径显示其区域设置并避免无限重定向循环。
搜索节点源
/api/1.0/nodes-sources/search
:使用 Apache Solr 引擎针对search
参数搜索所有节点源
如果您的搜索参数长度超过 3 个字符,则每个 API 结果项将包含
{ "nodeSource": { ... }, "highlighting": { "collection_txt": [ "In aliquam at dignissimos quasi in. Velit et vero non ut quidem. Sunt est <span class=\"solr-highlight\">tempora</span> sed. Rem nam asperiores modi in quidem quia voluptatum. Aliquid ut doloribus sit et ea eum natus. Eius commodi porro" ] } }
过滤器
- itemsPerPage:
int
- page:
int
- _locale:
string
如果未设置_locale,Roadiz将与现有的Accept-Language
头协商 - search:
string
- 标签:
array<string>
- node.parent:
int
或string
(节点名称) - node.visible:
bool
- node.nodeType:
array|string
通过类型筛选节点源搜索 - properties:
array
通过名称过滤序列化属性
按节点类型列出标签
/api/1.0/{node-type-name}/tags
:从给定类型中获取节点源中使用的所有标签。
如果您创建了 Event
节点类型,您可能想要列出任何附加到 事件 的 Tags
,API 可在 /api/1.0/event/tags
端点处使用。请注意,此端点将显示所有标签,无论是可见的还是不可见的,除非您对它们进行筛选。
过滤器
- itemsPerPage:
int
- page:
int
- _locale:
string
如果未设置_locale,Roadiz将与现有的Accept-Language
头协商 - search:
string
:这将搜索tagName
和翻译name
- order:
array
示例order[position]: ASC
值ASC
DESC
- node.parent:
int
或string
(节点名称) - node.tags.tagName:
int
或string
,或array
(标签名称) - parent:
int
或string
(标签名称) - properties:
array
通过名称过滤序列化属性
在 Tag
内容上
- tagName:
string
- parent:
int
或string
(标签名称) - visible:
bool
按节点类型列出存档
/api/1.0/{node-type-name}/archives
:从给定类型中获取节点源中使用的所有出版月份。
如果您创建了 Event
节点类型,您可能想要列出任何 事件 的存档,API 可在 /api/1.0/event/archives
端点处使用。以下是一个示例响应,按年份列出所有存档
{ "hydra:member": { "2021": { "2021-01": "2021-01-01T00:00:00+01:00" }, "2020": { "2020-12": "2020-12-01T00:00:00+01:00", "2020-10": "2020-10-01T00:00:00+02:00", "2020-07": "2020-07-01T00:00:00+02:00" } }, "@id": "/api/1.0/event/archives", "@type": "hydra:Collection", "hydra:view": { "@id": "/api/1.0/event/archives", "@type": "hydra:PartialCollectionView" } }
过滤器
- _locale:
string
如果未设置_locale,Roadiz将与现有的Accept-Language
头协商 - 标签:
array<string>
- tagExclusive:
bool
- node.parent:
int
或string
(节点名称)
获取节点源详情
/api/1.0/{node-type-name}/{id}/{_locale}
:获取具有节点 ID 和翻译locale
的节点源。这是用于生成您的内容 JSON-LD@id
字段的默认路由。/api/1.0/{node-type-name}/{id}
:获取具有节点 ID 和系统 默认 区域设置(或查询字符串)的节点源。/api/1.0/{node-type-name}/by-slug/{slug}
:获取具有其 slug(nodeName
或urlAlias
)的节点源
对于每个节点源,API 将在 /api/1.0/event/{id}
和 /api/1.0/event/by-slug/{slug}
端点处公开详细内容。
从其 path
直接获取节点源详细信息
/api/1.0/nodes-sources/by-path/?path={path}
:根据其path
(包括主页根路径)获取一个节点源的详细信息
过滤器
- properties:
array
通过名称过滤序列化属性
备用资源 URL
任何节点源详细响应都将包含一个带有所有替代翻译URL的Link
头。例如,一个翻译成英语和法语的法律页面将包含这个Link
头数据。
<https://api.mysite.test/api/1.0/page/23/en>; rel="alternate"; hreflang="en"; type="application/json",
<https://api.mysite.test/api/1.0/page/23/fr>; rel="alternate"; hreflang="fr"; type="application/json",
</mentions-legales>; rel="alternate"; hreflang="fr"; type="text/html",
</legal>; rel="alternate"; hreflang="en"; type="text/html"
text/html资源的URL始终是绝对路径,而不是绝对URL,以便在不携带API方案的情况下,在你的前端框架中生成自己的URL。
列出节点源子项
出于安全考虑,我们不会自动嵌入节点源子项。我们邀请您使用TreeWalker库来扩展您的JSON序列化,为您的每个节点类型构建一个安全的图。创建一个JMS\Serializer\EventDispatcher\EventSubscriberInterface
订阅者来扩展serializer.post_serialize
事件,并使用StaticPropertyMetadata
。
# Any JMS\Serializer\EventDispatcher\EventSubscriberInterface implementation… $exclusionStrategy = $context->getExclusionStrategy() ?? new \JMS\Serializer\Exclusion\DisjunctExclusionStrategy(); /** @var array<string> $groups */ $groups = $context->hasAttribute('groups') ? $context->getAttribute('groups') : []; $groups = array_unique(array_merge($groups, [ 'walker', 'children' ])); $propertyMetadata = new \JMS\Serializer\Metadata\StaticPropertyMetadata( 'Collection', 'children', [], $groups ); # Check if virtual property children has been requested with properties[] filter… if (!$exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) { $blockWalker = BlockNodeSourceWalker::build( $nodeSource, $this->get(NodeSourceWalkerContext::class), 4, // max graph level $this->get('nodesSourcesUrlCacheProvider') ); $visitor->visitProperty( $propertyMetadata, $blockWalker->getChildren() ); }
序列化上下文
在serializer.post_serialize
事件期间,序列化上下文在每次请求中都会持有许多有用的对象。
request
:Symfony当前请求对象nodeType
:初始节点源类型(如果不适用则为null
)cache-tags
:在序列化图期间填充的缓存标签集合translation
:当前请求的翻译groups
:当前请求的序列化组- 在列出节点源请求期间的序列化组
nodes_sources_base
document_display
thumbnail
tag_base
nodes_sources_default
urls
meta
- 在单个节点源请求期间的序列化组:单个节点源请求:-
walker
:rezozero树遍历器 -children
:rezozero树遍历器 -nodes_sources
-nodes_sources_single
:仅用于在主实体上显示自定义对象 -document_display
-thumbnail
-url_alias
-tag_base
-urls
-meta
-breadcrumbs
:仅在详细请求上允许面包屑
- 在列出节点源请求期间的序列化组
# Any JMS\Serializer\EventDispatcher\EventSubscriberInterface implementation… public function onPostSerialize(\JMS\Serializer\EventDispatcher\ObjectEvent $event): void { $context = $event->getContext(); /** @var \Symfony\Component\HttpFoundation\Request $request */ $request = $context->hasAttribute('request') ? $context->getAttribute('request') : null; /** @var \RZ\Roadiz\Contracts\NodeType\NodeTypeInterface|null $nodeType */ $nodeType = $context->hasAttribute('nodeType') ? $context->getAttribute('nodeType') : null; /** @var \RZ\Roadiz\Core\AbstractEntities\TranslationInterface|null $translation */ $translation = $context->hasAttribute('translation') ? $context->getAttribute('translation') : null; /** @var array<string> $groups */ $groups = $context->hasAttribute('groups') ? $context->getAttribute('groups') : []; }
面包屑
如果您希望API为每个可达的节点源提供面包屑,您可以实现Themes\AbstractApiTheme\Breadcrumbs\BreadcrumbsFactoryInterface
并将其注册在您的AppServiceProvider
中。对于每个NodeTypeSingle API请求(即在列表上下文之外),将注入一个包含在您的BreadcrumbsFactoryInterface中定义的所有节点父级的breadcrumbs
。
以下是一个尊重Roadiz节点树结构的纯实现。
<?php declare(strict_types=1); namespace App\Breadcrumbs; use RZ\Roadiz\Core\Entities\NodesSources; use Themes\AbstractApiTheme\Breadcrumbs\BreadcrumbsFactoryInterface; use Themes\AbstractApiTheme\Breadcrumbs\BreadcrumbsInterface; use Themes\AbstractApiTheme\Breadcrumbs\Breadcrumbs; final class BreadcrumbsFactory implements BreadcrumbsFactoryInterface { /** * @param NodesSources|null $nodesSources * @return BreadcrumbsInterface|null */ public function create(?NodesSources $nodesSources): ?BreadcrumbsInterface { if (null === $nodesSources || null === $nodesSources->getNode() || null === $nodesSources->getNode()->getNodeType() || !$nodesSources->getNode()->getNodeType()->isReachable()) { return null; } $parents = []; while (null !== $nodesSources = $nodesSources->getParent()) { if (null !== $nodesSources->getNode() && $nodesSources->getNode()->isPublished() && $nodesSources->getNode()->isVisible()) { $parents[] = $nodesSources; } } return new Breadcrumbs(array_reverse($parents)); } }
# App\AppServiceProvider $container[BreadcrumbsFactoryInterface::class] = function (Container $c) { return new BreadcrumbsFactory(); };
错误
如果您想在JSON中获取详细的错误信息,请不要忘记在每个请求中添加头:Accept: application/json
。您将得到类似的消息
{ "error": "general_error", "error_message": "Search engine does not respond.", "message": "Search engine does not respond.", "exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException", "humanMessage": "A problem occurred on our website. We are working on this to be back soon.", "status": "danger" }
带有正确的状态码(40x或50x)。确保在请求失败时从您的客户端框架中捕获并读取您的响应数据,以了解更多错误信息。
使用 Etags
每个基于NodeSources的响应都将包含一个基于API响应内容校验和计算的ETag
头。
您可以将您的API消费者设置为发送包含最新ETag的If-None-Match
头。如果内容未更改,API将返回一个空的304 Not Modified响应;如果内容已更改,则返回带有新ETag头的整个响应。