rosengate/sigil

Laravel-Exedra PHP 8 属性路由控制器包

v0.0.5 2024-08-01 17:51 UTC

This package is auto-updated.

Last update: 2024-09-01 17:57:12 UTC


README

Laravel-Exedra PHP 8 属性路由控制器包

目录

特性

  • 将路由与控制器类耦合
  • 基于 PHP 8 属性的路由组件
  • 嵌套路由
  • 提供灵活的方式来通过以下方式控制/设计您的应用程序:
    • 基于嵌套路由的中间件
    • 元信息
    • 创建自己的属性,并通过自己的中间件控制它

要求

  • Laravel
  • PHP >= 8
  • 此包覆盖了您的 Laravel Http Kernel,并完全使用来自 rosengate/exedra 的路由/控制器/中间件组件,但如果没有匹配的路由,它仍然回退到 Laravel 路由。

设置

1. 通过 composer 安装包

对于 Laravel 10 及以下

composer require rosengate/sigil

对于 Laravel 11 及以上

composer require psr/http-message ^1.1
composer require rosengate/sigil

2. 注册 Sigil\SigilProvider

对于 Laravel 10 及以下

在您的 config\app.php 中注册 Sigil\Providers\SigilProvider

    /*
     * Package Service Providers...
     */
    SigilProvider::class,

对于 Laravel 11 及以上,您可以在 bootstrap/providers.php 下注册此内容

3. 发布并缓存配置

运行 vendor:publish

php artisan vendor:publish --provider=Sigil\SigilProvider

配置缓存

php artisan config:cache

4. Http Kernel 扩展

由于此包在 HTTP 层面上进行交互,Sigil 通过 Laravel Http Kernel 进行自己的桥接。

对于 Laravel 10 及以下,通过 Sigil\SigilKernel 扩展您的 App\Http\Kernel(因为此包使用自己的路由和请求分发)。

<?php
namespace App\Http;

class Kernel extends \Sigil\SigilKernel {
}

对于 Laravel 11 及以上,您可以访问您的 bootstrap/app.php 并将 Application 替换为 Sigil\SigilApplication。此替换自行处理内核扩展。

基本用法

随着您的安装提供的是根控制器,您将在其中定义初始路由。

namespace App\Http\Controllers;

class RootController extends \Sigil\Controller
{
    public function groupWeb()
    {
        return WebController::class;
    }
}

第二个控制器 WebController 将是您的应用的面向前端的控制器(遵循 Laravel 类似的路由)

namespace App\Http\Controllers;

use Exedra\Routeller\Attributes\Path;

#[Path('/')]
class WebController extends \Sigil\Controller
{
    #[Path('/')]
    public function get()
    {
        return view('welcome');
    }
}

路由约定

通过控制器构建的路由注册基于约定和方法名的前缀。

只为特定的(REST)方法创建操作

  • get()post()delete()patch()put()
  • 也可以附加额外的字符串,例如 getUsers()
示例
<?php
namespace App\Http\Controllers;

use Exedra\Routeller\Attributes\Path;


#[Path('/')]
class WebController extends \Sigil\Controller
{
    #[Path('/contact-us')]
    public function getContactUs()
    {
    }
    
    #[Path('/contact-us')]
    public function postContactUs()
    {
    }
}

为任何方法创建操作

  • 使用 execute 前缀 WITH 附加字符串
    • 例如 executeContactUs()
示例
<?php
namespace App\Http\Controllers;

use Exedra\Routeller\Attributes\Path;


#[Path('/')]
class WebController extends \Sigil\Controller
{
    #[Path('/about-us')]
    public function executeAboutUs()
    {
    }
}

创建基于方法的中间件

  • middlewaremiddlewareAuth

示例

创建路由分组

嵌套路由

  • 使用 group 前缀 WITH 附加字符串
    • 例如 groupBook

示例

路由设置

  • 如果您希望进行更程序化的路由,可以创建一个 setup(Group $router) 方法。

路由属性

  • Path(string path) 定义当前路由/路由组的路径(相对路径)
  • Method(string method|array methods) 设置方法可访问性
  • Name(string name) 设置路由名称
  • Tag(string tag) 标记当前路由
  • Middleware(string middlewareClass) 添加中间件
  • State(string key, mixed value) 可变元信息
  • Series(string key, mixed value) 可加元信息
  • Flag(mixed flag) 元信息的数组
  • Requestable(bool) 设置此路由是否可请求
  • AsFailRoute 将选定的路由标记为后备路由

子路由/分组

该包允许您无限地将路由嵌套在其他路由之下。您的路由URI/路径是相对于深度递减的。

创建一个以 group 为前缀的方法,并返回控制器的名称。

<?php
use Exedra\Routeller\Attributes\Path;

class RootController extends \Sigil\Controller
{
    public function groupWeb()
    {
        return WebController::class;
    }
    
    public function groupAdmin()
    {
        return AdminController::class;
    }
}

#[Path('/admin')]
class AdminController extends \Sigil\Controller
{
    #[Path('/dashboard')]
    public function getDashboard()
    {
    }
}

#[Path('/')]
class WebController extends \Sigil\Controller
{
    public function groupEnquiries()
    {
        return EnquiriesController::class;
    }
}

#[Path('/enquiries')]
class EnquiriesController extends \Sigil\Controller
{
    #[Path('/form')]
    public function getForm()
    {
    }
    
    #[Path('/form')]
    public function postForm()
    {
    }
}

路由结果将如下

GET  /admin/dashboard
GET  /enquries/form
POST /enquries/form

中间件

您可以使用Laravel中间件,因为它仍然遵循相同的签名,构造函数参数也由Laravel di容器注入。

全局中间件

如果您遵循上述 SigilSetup(通过提供中间件类数组),您只需维护 App\Http\Kernel$middleware 属性中的中间件列表即可。

基于分组/路由的中间件

基于类的中间件。

<?php
namespace App\Http\Controllers;

use Exedra\Routeller\Attributes\Path;
use Exedra\Routeller\Attributes\Middleware;
use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Session\Middleware\StartSession;
use App\Http\Middleware\EncryptCookies;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;

#[Middleware(EncryptCookies::class)]
#[Middleware(AddQueuedCookiesToResponse::class)]
#[Middleware(StartSession::class)]
class WebController extends \Sigil\Controller
{
    #[Path('/contact-us')]
    #[Middleware(VerifyCsrfToken::class)]
    public function postContactUs()
    {
    }
}

基于方法的中间件

您可以直接在控制器中创建中间件,方法名前缀为 middleware。在此过程中,您还可以通过方法参数注入任何已注册的实例。

<?php
namespace App\Http\Controllers;

//.. imports

#[Path('/blogs/:blog-id')]
class BlogApiController extends \Sigil\Controller
{
    public function middleware(Request $request, $next, BloggerModel $blogger)
    {
        $blog = BlogModel::findOrFail($request->route('blog-id'));
        
        app()->instance(BlogRepository::class, new BlogRepository($blogger, $blog));
    
        return $next($request);
    }
    
    #[Path('/articles')]
    public function getArticles(BlogRepository $blogService)
    {
        return $blogService->getArticles();
    }
}

此方法通过使用中间件,让您对当前路由的上下文有更多的控制。

元信息

该框架的嵌套特性使我们能够以我们希望的方式设计我们的应用程序。然而,我们可以使用三种类型的信息为此目的。

状态

基于可变键的信息。

<?php
//...
use Sigil\Context;
use Exedra\Routeller\Attributes\State;
use Exedra\Routeller\Attributes\Path;

#[Path('/')]
#[State('is_ajax', true)]
class WebController extends \Sigil\Controller
{
    #[Path('/contact-us')]
    #[State('is_ajax', false)]
    public function getContactUs(Context $context)
    {
        var_dump($context->getState('is_ajax')); // false
    }
}

系列

基于可加/数组键的特定信息。新信息是追加的,而不是修改的。

<?php
use Sigil\Context;
use Exedra\Routeller\Attributes\Path;
use Exedra\Routeller\Attributes\Series;

#[Series('roles', 'admin')]
class AdminController extends \Sigil\Controller
{
    #[Path('/dashboard')]
    #[Series('roles', 'librarian')]
    public function getLibrary(Context $context)
    {
        var_dump($context->getSeries('roles')); // prints ['admin', 'staff']
    }
    
    #[Series('roles', 'accountant')]
    #[Path('/billing')]
    public function getOrders(Context $context)
    {
        var_dump($context->getSeries('roles')); // prints ['admin', 'accountant']
    }
}

标志

标志/信息的数组。与系列类似,但更简单。

<?php
use Exedra\Routeller\Attributes\Flag;
use Exedra\Routeller\Attributes\Path;
use Sigil\Context;

#[Flag('authenticated')]
class AdminController extends \Sigil\Controller
{
    #[Path('/dashboard')]
    #[Flag('is_beta')]
    public function getDashboard(Context $context)
    {
        var_dump($context->getFlags()); // ['authenticated', 'is_beta']
    }
}

元信息的使用

元信息最好与中间件一起使用,您可以通过定义的元来控制应用程序的流程/行为/设计。

例如,让我们使用上面编写的一些元信息并编写一些伪代码。

use Sigil\Context;
use App\Models\User;

class RootController extends \Sigil\Controller
{
    public function middleware($request, $next, Context $context)
    {
        if ($context->hasFlag('authenticated')) {
            // do some authentication
            if (!session()->has('user_id'))
                throw new NotAuthenticatedException();
                
            $user = User::find(session()->get('user_id'));
            
            if ($context->hasFlag('is_beta')) {
                if (!$user->isBetaAllowed())
                    throw new NoAccessException();
            }
            
            if ($context->hasSeries('roles'))
                if (!in_array($user->role, $context->getSeries('roles')))
                    throw new NoAccessException();
        }
         
        return $next($request);
    }
}

创建自己的属性

创建自己的属性最简单的方法是通过扩展这些元信息并按自己的术语使用它们。

例如,我们想要一个属性来决定哪些路由指向哪些用户角色。

<?php
namespace App\Attributes;

use Exedra\Routeller\Attributes\Series;

#[\Attribute]
class Role extends Series
{
    public function __construct($role)
    {
        parent::__construct(static::class, $role);
    }
}

创建一个中间件来利用此信息。

<?php
namespace App\Http\Middleware;
use Sigil\Context;
use \App\Attributes\Role;

class RolesCheckMiddleware
{
    public function handle($request, $next, Context $context)
    {
        if ($roles = $context->getSeries(Role::class)) {
            //.. do a check if user has these roles
        }
        
        return $next($request);
    }
}

然后将其添加到您的 App\Http\Kernel

现在您可以在任何控制器中使用此属性。

<?php
namespace App\Http\Controllers;

use Exedra\Routeller\Attributes\Path;
use App\Attributes\Role;

#[Path('/accounts')]
#[Role('accountant')]
class ManageAccountsController extends \Sigil\Controller
{
    public function get()
    {
    }
}

DI方法注入

该包的DI连接利用了Laravel容器注册。因此,您可以在app()容器上注册的任何内容也可以在这里检索。

例如

<?php
use Exedra\Routeller\Attributes\Path;

#[Path('/books/:book-id')]
class BookApiController extends \Sigil\Controller
{
    public function middleware($request, $next)
    {
        app()->instance(Book::class, Book::findOrFail($request->route('book-id')));
        
        return $next($request);
    }
    
    #[Path('/')]
    public function get(Book $book)
    {
        return $book;
    }
    
    #[Path('/')]
    public function post(Book $book, $request)
    {
        $book->author = $request->author;
        $book->isbn = $request->isbn;
        $book->save();
        
        return $book;
    }
}

实用工具

路由-模型查找/注册

示例用法

<?php
use Exedra\Routeller\Attributes\Path;
use Sigil\Utilities\Attributes\Model;
use App\Models\AuthorModel;

#[Path('/authors/:author-id')]
#[Model(AuthorModel::class, 'author-id')]
class AuthorApiController extends \Sigil\Controller
{
    public function get(AuthorModel $author)
    {
        return $author;
    }
}
处理异常

您可以通过创建一个中间件来捕获这种未找到模型异常来处理模型未找到异常。

例如。

<?php

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class RootController extends \Sigil\Controller
{
    public function middleware(Request $request, $next)
    {
        try {
            return $next($request);
        } catch (ModelNotFoundException $e) {
            //.. do something
        }
    }
}

PHPLeague Transformer

PHP League Fractal 转换器。通过在您的动作上添加注解,将您的 Laravel 模型/集合的 API 响应进行转换。此包使用 spatie/laravel-fractal

用法

<?php
use Exedra\Routeller\Attributes\Path;
use Sigil\Utilities\Attributes\Model;
use App\Models\OrderModels;
use App\Transformers\OrderTransformer;

#[Path('/orders/:order-id')]
#[Model(OrderModel::class, 'order-id')]
class OrderApiController extends \Sigil\Controller
{
    #[Transform(OrderTransformer::class)]
    public function get(OrderModel $order)
    {
        return $order;
    }
}

渲染器

通过定义一个实现 Sigil\Contracts\Renderer 的渲染器来处理控制器动作返回的内容。

设置

控制台命令

列出路由

列出所有路由

php artisan sigil:routes

筛选 web. 路由下的路由

php artisan sigil:routes --name=web

待办事项

  • laravel url 生成器兼容性
  • 更好的安装/设置流程
  • 相关的 artisan 命令
    • 路由列表
    • 创建控制器
  • 缓存策略/测试
  • 更稳定的版本

缺点

目前,当没有匹配的路由时,此包将回退到 Laravel 路由。

为什么

我在 4 年前编写了 rosengate/exedra,因为我找不到一个可以完全满足我需求的框架,比如在另一个路由下嵌套层次化的路由。此外,exedra 从未打算成为另一个完整的框架。它只是一个微框架,我总是倡导使用大量优秀的 PHP 包。然后我构建了一个基于 phpdoc 的路由控制器组件,从那时起,用 exedra 编写代码变得比以往任何时候都更加愉快。但构建微框架中的东西可能会令人望而却步,因为我始终需要一个 ORM、验证、错误处理以及许多其他工具(我总是使用 Elqouent)。

然后,在某一点上,我开始非常习惯于 Laravel,并决定尝试将其与 exedra 结合使用。我开始认为这可能是有可能的。然后 PHP8 带来了一个我等了多年的好消息。属性/注解。所以我决定只是将其移植到 Laravel 并看看会怎样。<3

反馈

  • 请通过 GitHub 问题提交反馈。 我计划尽快找到一种方法来集成 Illuminate\Contracts\Routing\UrlGenerator

许可证

MIT 许可证