bedita / web-tools

用于使用 BEdita API 的 CakePHP 应用程序的工具

安装次数: 60,645

依赖项: 2

建议者: 0

安全性: 0

星标: 3

关注者: 5

分支: 3

开放问题: 4

类型:cakephp-plugin

v4.0.2 2024-09-16 10:21 UTC

README

Github Actions codecov phpstan Scrutinizer Code Quality image image

安装

首先,如果 vendor 目录尚未创建,您必须使用以下命令安装 composer 依赖项

composer install

您可以使用 composer 将此插件安装到您的 CakePHP 应用程序中。

安装 composer 包的推荐方法是

composer require bedita/web-tools

辅助工具

Web 组件

此助手提供了一些方法,通过在客户端 JavaScript 组件中设置一些应用变量来设置自定义元素。它旨在避免使用声明性赋值到 HTML 节点来生成内联 JS 字典或变量。字符串和数值作为节点属性添加,而对象和数组使用内联脚本。

示例

webroot/js 中创建一个包含自定义元素定义的 js 文件

webroot/js/components/awesome-video.js

class AwesomeVideo extends HTMLElement {
    connectedCallback() {
        this.video = document.createElement('video');
        this.video.src = this.getAttribute('src');
        this.appendChild(this.video);
    }
}

customElements.define('awesome-video', AwesomeVideo);

现在您可以在 twig 模板中初始化元素

templates/Pages/document.twig

{{ WebComponents.element('awesome-video', { src: attach.uri }, 'components/awesome-video') }}

您还可以通过使用 is 方法扩展原生标签,以便设置与 is 方法简单交互

webroot/js/components/awesome-table.js

class AwesomeTable extends HTMLElement {
    connectedCallback() {
        this.addEventListener('click', (event) => {
            let th = event.target.closest('[sort-by]');
            if (th) {
                this.sortBy(th.getAttribute('sort-by'));
            }
        }):
    }

    sortBy(field) {
        // ...
    }
}

customElements.define('awesome-table', AwesomeTable, { extends: 'table' });

templates/Pages/users.twig

<table {{ WebComponents.is('awesome-table', {}, 'components/awesome-table')|raw }}>
    <thead>
        <th>Email</th>
        <th>Name</th>
    </thead>
    <tbody>
        {% for user in users %}
            <tr>
                <td>{{ user.attributes.email }}</td>
                <td>{{ user.attributes.name }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>

使用 AssetRevisions 加载资源

AssetRevisions 通过资产策略可以帮助轻松解决加载构建版本化资源(如 jscss)的常见问题。

通过 \BEdita\WebTools\View\Helper\HtmlHelper,您可以透明地链接放置在自定义文件夹中或生活在 webroot/jswebroot/css 中的原始资源。

定义要使用的策略

定义应用程序将使用哪种策略的最佳位置是 Application::bootstrap()

use BEdita\WebTools\Utility\AssetRevisions;
use BEdita\WebTools\Utility\Asset\Strategy\EntrypointsStrategy;

public function bootstrap(): void
{
    parent::bootstrap();

    AssetsRevisions::setStrategy(new EntrypointsStrategy());

    // you can also set the path where to find the manifest (default is webroot/build/entrypoints.json)
    // AssetsRevisions::setStrategy(
    //     new EntrypointsStrategy(['manifestPath' => '/custom/path/entrypoints.json']);
    // );
}

有三种资产策略可用

  • 基于由 Webpack Encore 生成的 entrypoints.json 文件的 EntrypointsStrategy
  • 基于由 gulp-rev 生成的 rev-manifest.json 文件的 RevManifestStrategy

无论如何,您都可以通过实现 AssetStrategyInterface 来定义自己的策略。

使用 HtmlHelper 加载资源

一旦设置策略,您就可以使用 \BEdita\WebTools\View\Helper\HtmlHelper 及其方法 script()css()assets() 链接资源,例如

<?= $this->Html->script('app') ?>

将首先从您的资产策略中搜索 app 资产,如果策略无法解析资产,则回退到 CakePHP HtmlHelper

这样,您可以继续以在常见的 webroot/jswebroot/css 中放置的方式加载资源,并将解析链接的任务委托给 \BEdita\WebTools\View\Helper\HtmlHelper

标识符

ApiIdentifier

ApiIdentifier标识符,它帮助通过 BEdita API 识别 Authentication 插件。

为了使用标识符,您需要在 Application.php 应用程序引导中安装和加载 Authentication 插件

安装

composer require cakephp/authentication

然后在应用中加载

public function bootstrap(): void
{
    parent::bootstrap();

    $this->addPlugin('Authentication');
}

然后添加 AuthenticationMiddleware

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    // Various other middlewares for error handling, routing etc. added here.

    // Create an authentication middleware object
    $authentication = new AuthenticationMiddleware($this);

    // Add the middleware to the middleware queue.
    // Authentication should be added *after* RoutingMiddleware.
    // So that subdirectory information and routes are loaded.
    $middlewareQueue->add($authentication);

    return $middlewareQueue;
}

并利用 getAuthenticationService() 钩子来设置标识符。

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();

    // Load the authenticators, you want session first
    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form', [
        'loginUrl' => '/users/login'
    ]);

    // Load identifiers
    $service->loadIdentifier('BEdita/WebTools.Api');

    return $service;
}

身份和身份助手

要使用它们,请确保安装 Authentication 插件

composer require cakephp/authentication

并在 Application::bootstrap() 中加载插件 $this->addPlugin('Authentication')

然后设置您的应用程序以使用 Identity,例如

// Edit Application.php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService([
        'identityClass' => \BEdita\WebTools\Identity::class
    ]);

    // Load the authenticators, you want session first
    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form');

    // Load identifiers
    $service->loadIdentifier('BEdita/WebTools.Api');

    return $service;
}

Identity 提供了一个方便的 hasRole() 方法

// in a Controller
$identity = $this->Authentication->getIdentity();
if ($identity->hasRole('admin')) {
    $this->Flash->success('Hi admin!');
}

IdentityHelper 允许将配置的方法委派给 Identity,例如在 TwigView 模板中

{% if Identity.hasRole('basic') %}
    <button type="button">Upgrade to premium</button>
{% endif %}

请求策略

使用 RequestPolicy 类,可以通过身份的角色或自定义策略规则设置对控制器和操作的访问。

首先安装 Authorization 插件

composer require cakephp/authorization

并在 Application::bootstrap() 中加载插件 $this->addPlugin('Authorization')

然后在 Application 类中设置策略。添加 AuthorizationMiddlewareRequestAuthorizationMiddleware

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue {
    // other middleware...
    // $middlewareQueue->add(new AuthenticationMiddleware($this));

    // Add authorization (after authentication if you are using that plugin too).
    $middlewareQueue->add(new AuthorizationMiddleware($this));
    $middlewareQueue->add(new RequestAuthorizationMiddleware());
}

并配置策略

public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
    $mapResolver = new MapResolver();
    $mapResolver->map(
        ServerRequest::class,
        new RequestPolicy([
            // setup your request policy rules
            'rules' => [
                'Dashboard' => [
                    'index' => ['premium', 'basic'], // allow access to DashboardController::index() for these roles
                    'special' => 'premium', // allow access to Dashboard::special() only for 'premium' role
                    '*' => false, // fallback for other DashboardController actions. Forbidden to all
                ],
            ],
        ]);
    );

    return new AuthorizationService($mapResolver);
}

OAuth2 设置

使用提供的 OAuth2 工具的快速步骤。

  1. config/routes.php 中创建一个指向类似 /ext/login/{provider} 的路径,以与所选的 OAuth2 提供者交互。每个 {provider} 必须与配置中指定的提供者配置键匹配,例如下面配置示例中的 google,请参阅 OAuth2 提供者结构。以下是一个示例
        $builder->connect(
            '/ext/login/{provider}',
            ['controller' => 'ExternalLogin', 'action' => 'login'],
            ['_name' => 'login:oauth2']
        );
  1. 为上述路由规则定义一个控制器。登录操作的简化版本可以包含如下示例中的简单重定向
    public function login(): ?Response
    {
        $result = $this->Authentication->getResult();
        if (!empty($result) && $result->isValid()) {
            $target = $this->Authentication->getLoginRedirect() ?? ['_name' => 'home'];

            return $this->redirect($target);
        }
        // Handle authentication failure below with flash messages, logs, redirects...
        // ....
    }
  1. 在您的主 Application 类中设置 OAuth2AuthenticatorOAuth2Identifier 类,并创建相应的 OAuth2Providers 配置。下面段落中有更多详细信息。

  2. Application::middleware() 中将 OAuth2Middleware 添加到中间件堆栈中,紧随 AuthenticationMiddleware 之后,如下所示

            ->add(new AuthenticationMiddleware($this))
            ->add(new OAuth2Middleware())

OAuth2 提供者

要使用 OAuth2AuthenticatorOAuth2Identifier 类,您必须在将此类加载到身份验证服务中时传递支持的 OAuth2 提供者配置。以下是一个在 Application::getAuthenticationService() 中如何操作的简要示例

    $service = new AuthenticationService();

    $path = $request->getUri()->getPath();
    if (strpos($path, '/ext/login') === 0) {
        $providers = (array)Configure::read('OAuth2Providers');
        $service->loadIdentifier('BEdita/WebTools.OAuth2', compact('providers') + [
            'autoSignup' => true,
            'signupRoles' => ['customer'],
        ]);
        $service->loadAuthenticator('BEdita/WebTools.OAuth2', compact('providers') + [
            'redirect' => ['_name' => 'login:oauth2'],
        ]);
    }

我们仅在请求路径与上面定义的 OAuth2 登录路由匹配时设置 OAuth2 验证器和标识符。

建议使用配置键如 OAuth2Providers 来存储提供者信息,但您必须使用 providers 键传递提供者设置数组。其他可能的配置是

  • (OAuth2Authenticator) 'redirect' - 默认 ['_name' => 'login'],指定为命名数组的重定向 URL 路由
  • (OAuth2Identifier) autoSignup - 默认 false,如果希望登录失败时执行自动注册,则设置为 true
  • (OAuth2Identifier) 'signupRoles' - 默认 [],在注册过程中使用的用户角色,仅当 autoSignuptrue 时使用

OAuth2 提供者结构

以下示例中的提供者配置结构如下。这里定义了一个单个的 google 提供者。必选配置键是 classsetupoptionsmap,下面将解释。每个提供者键必须与在 BEdita API 中定义和配置的 auth_provider 名称匹配。

    'google' => [
        // OAuth2 class name, must be a supported provider of `league/oauth2-client`
        // see https://oauth2-client.thephpleague.com/providers/league/ official or third-party
        'class' => '\League\OAuth2\Client\Provider\Google',

        // Provider class setup parameters, normally this includes `clientId` and `clientSecret` keys
        // Other parameters like 'redirectUri' will be added dynamically
        'setup' => [
            'clientId' => '####',
            'clientSecret' => '####',
        ],

        // Provider authorization options, specify the user information scope that you want to read
        // `'scope'` array will vary between providers, please read the oauth2-client documentation.
        'options' => [
            'scope' => ['https://www.googleapis.com/auth/userinfo.email'],
        ],

        // Map BEdita user fields with auth provider data path, using dot notation like 'user.id'
        // In this array keys are BEdita fields, and values are paths to extract the desired item from the provider response
        // only `'provider_username'` is mandatory, to uniquely identify the user in the provider context
        // other fields could be used during signup
        'map' => [
            'provider_username' => 'sub',
            'username' => 'email',
            'email' => 'email',
            'name' => 'given_name',
            'surname' => 'family_name',
        ],
    ],

有关 OAuth2 提供者配置的简要参考,请参阅 OAuth2 提供者配置 Wiki 页面