Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Curly Brackets ({}) Syntax #27

Open
ghostwriter opened this issue Aug 27, 2024 · 4 comments
Open

Support Curly Brackets ({}) Syntax #27

ghostwriter opened this issue Aug 27, 2024 · 4 comments

Comments

@ghostwriter
Copy link
Owner

ghostwriter commented Aug 27, 2024

Segment: A portion of a URL path that can contain dynamic or static content.

For example, in the URL https://{app}.example.com/users/{userId}/profile/{section}, {app}, /users and /profile are segments.

The segment {app} represents a dynamic part of the URL that can change based on user input.


Parameter: A variable in the URL that can represent different values.

Parameters can be used within segments to capture dynamic values or provide additional information.

For example, in the URL /search?q={query}, q is a query parameter that can be replaced with different search terms.


Name Syntax Description
Named Parameter /users/{userId} Matches a segment with a specific name, e.g., {userId}.
Regex Parameter /products/{productId:[0-9]+} Matches a segment using a regex pattern, e.g., {productId:[0-9]+}.
Wildcard Parameter /files/{path:*} Captures multiple segments, e.g., {path*}.
Optional Parameter /posts/{postId?} Matches a segment that is optional, e.g., {postId?}.
Query Parameter /search?q={query} Matches a query parameter in the URL, e.g., ?q={query}.
Matrix Parameter /items;color={color};size={size} Matches parameters in the matrix format, e.g., ;color={color};size={size}.
Static Segment /static/about Matches a static segment exactly, e.g., /static/about.
Dynamic Segment /users/{id}/profile/{section} Matches dynamic segments in the URL, e.g., /users/{id}/profile/{section}.
Subdomain Segment {tenant}.example.com Matches subdomains, e.g., {tenant}.example.com.
Hyphenated Segment /flights/{from}-{to} Matches hyphenated segments, e.g., /flights/{from}-{to}.
Dot-separated Segment /files/{category}.{filename} Matches dot-separated segments, e.g., /files/{category}.{filename}.
@ghostwriter ghostwriter changed the title Support **Curly Brackets** ({}) Syntax Support Curly Brackets ({}) Syntax Aug 27, 2024
@ghostwriter
Copy link
Owner Author

<?php

declare(strict_types=1);

namespace Ghostwriter\Router\Interface;

use Closure;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

interface RouteCollectorInterface
{
    public function add(RouteInterface $route): void;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function any(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function delete(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                       $name
     * @param Closure(RouteCollectorInterface): void $callback
     */
    public function domain(string $name, Closure $callback): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function get(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function head(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param list<class-string<MiddlewareInterface>> $middlewares
     * @param Closure(RouteCollectorInterface): void  $callback
     */
    public function middleware(array $middlewares, Closure $callback): self;

    /**
     * @param non-empty-string                       $name
     * @param Closure(RouteCollectorInterface): void $callback
     */
    public function name(string $name, Closure $callback): self;

    /**
     * @param non-empty-string                        $method
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function map(
        string $method,
        string $path,
        string $handler,
        string $name,
        array $middlewares = [],
    ): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function options(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function patch(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function post(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @param non-empty-string                      $path
     * @param Closure(RouteCollectorInterface):void $callback
     */
    public function prefix(string $path, Closure $callback): self;

    /**
     * @param non-empty-string                        $path
     * @param class-string<RequestHandlerInterface>   $handler
     * @param non-empty-string                        $name
     * @param list<class-string<MiddlewareInterface>> $middlewares
     */
    public function put(string $path, string $handler, string $name, array $middlewares = []): self;

    /**
     * @return array<non-empty-string,RouteInterface>
     */
    public function routes(): array;
}

@ghostwriter
Copy link
Owner Author

<?php

declare(strict_types=1);

namespace Ghostwriter\Router\Interface;

use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

interface RouteInterface
{
    /** @return non-empty-string */
    public function domain(): string;

    /** @return class-string<RequestHandlerInterface> */
    public function handler(): string;

    /** @return non-empty-string */
    public function method(): string;

    /** @return list<class-string<MiddlewareInterface>> */
    public function middlewares(): array;

    /** @return non-empty-string */
    public function name(): string;

    /** @return non-empty-string */
    public function path(): string;
}

@ghostwriter
Copy link
Owner Author

ghostwriter commented Aug 27, 2024

<?php

declare(strict_types=1);

namespace Ghostwriter\Router\Interface;

use Ghostwriter\Router\Interface\Exception\RouteMethodNotAllowedExceptionInterface;
use Ghostwriter\Router\Interface\Exception\RouteNotFoundExceptionInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;

interface RouterInterface
{
    public function add(RouteInterface $route): void;

    /**
     * @throws RouteNotFoundExceptionInterface
     * @throws RouteMethodNotAllowedExceptionInterface
     */
    public function match(ServerRequestInterface $serverRequest): RouteInterface;

    /**
     * @return array<non-empty-string,RouteInterface>
     */
    public function routes(): array;

    /**
     * Generate a URI from a named route.
     *
     * $router->add(Route::new('GET', '/post/{id}', 'PostHandler::class', 'post.show', ['AuthMiddleware::class'], 'localhost'))
     *
     * $router->uri('post.show', ['id' => 1], ['page' => 2], 'comments') => /post/1?page=2#comments
     *
     * @param array<string,scalar> $parameters
     * @param array<string,scalar> $query
     * 
     * @throws RouteNotFoundExceptionInterface
     */
    public function uri(
        string $name,
        array $parameters = [],
        array $query = [],
        string $fragment = ''
    ): UriInterface;
}

@ghostwriter
Copy link
Owner Author

final readonly class MethodOverrideMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        foreach (['X-HTTP-Method-Override', 'X-Method-Override', 'X-HTTP-Method', 'X-Method'] as $header) {
            if (! $request->hasHeader($header)) {
                continue;
            }
            return $handler->handle($request->withMethod(mb_strtoupper($request->getHeaderLine($header))));
        }

        $method = $request->getMethod();

        if ($method === 'POST') {
            $parsedBody = $request->getParsedBody();

            $method = $parsedBody['_method'] ?? $parsedBody['__METHOD__'] ?? $method;
        }

        // Continue processing with the updated request
        return $handler->handle($request->withMethod($method));
    }
}

@ghostwriter ghostwriter transferred this issue from another repository Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant