Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
alirezasalehizadeh committed Oct 31, 2023
0 parents commit c949679
Show file tree
Hide file tree
Showing 36 changed files with 1,374 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/.phpunit.result.cache
/.phpunit.cache
/.php-cs-fixer.cache
/.php-cs-fixer.php
/composer.lock
/phpunit.xml
/vendor/
*.swp
*.swo
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
- Adds first version
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CONTRIBUTING

Contributions are welcome, and are accepted via pull requests.
Please review these guidelines before submitting any pull requests.

## Process

1. Fork the project
2. Create a new branch
3. Code, test, commit and push
4. Open a pull request detailing your changes. Make sure to follow the [template](.github/PULL_REQUEST_TEMPLATE.md)

## Guidelines

* Send a coherent commit history, making sure each individual commit in your pull request is meaningful.
* You may need to [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) to avoid merge conflicts.
* Please remember that we follow [SemVer](http://semver.org/).

## Setup

Clone your fork, then install the dev dependencies:
```bash
composer install
```
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) Alireza Salehizadeh <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
146 changes: 146 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<p align="center">
<img src="art/routail.png"/>

A spidey PHP router like Tarantulas
</p>

## Features
* Support `get`, `post`, `put`, `patch`, `delete` and `any` method
* Support optional parameter
* Middlewares
* Route group
* Url generator
## Requirements
PHP >= 8.2


## Getting Started


#### Installation
via Composer
```
composer require alirezasalehizadeh/routail
```

#### Route definition
The example below, is a quick view of how you can define an route and run it
```php
use AlirezaSalehizadeh\Routail\Router;

$router = new Router();

$router->get(string $pattern, string|array|Closure $action)
->name(string $name)
->prefix(string $prefix)
->middleware(array $middlewares);

$router->run();

```

#### Route group definition
```php
use AlirezaSalehizadeh\Routail\Router;

$router = new Router();

$router->group(Closure $action, array $middlewares, string $prefix);

$router->run();

```
## Usage

#### Middlewares
To use middlewares, you need to create a class that extends from the `AlirezaSalehizadeh\Routail\Middleware` class and implement the `handle` method that returns a boolean
```php
use AlirezaSalehizadeh\Routail\Request;
use AlirezaSalehizadeh\Routail\Middleware\Middleware;

class FooMiddleware extends Middleware
{
public function handle(Request $request): bool
{
return true;
}
}

```

#### Url generator
By `url` method, you can create url from route name easily
```php
use AlirezaSalehizadeh\Routail\Router;

$router = new Router();

$router->get('/users/{id}', 'UserController@show')->name('user_show');

$router->url('user_show', ['id' => '1']);

// output: /users/1
```

#### Route parameter types
Route parameters can have a type, which can be optional
```
any
id
int
string
uuid
slug
bool
date
int? // optional
any? // optional
```
## Examples
```php
use AlirezaSalehizadeh\Routail\Router;

$router = new Router();

$router->get('/users', 'UserController@index');

$router->any('/users', [UserController::class, 'index']);

// route pattern with parameter
$router->get('/users/{id}', 'UserController@show');

// route pattern with parameter and type
$router->get('/users/{id:int}', function($id){
return "User id is $id";
});

// route pattern with optional parameter
$router->get('/users/{id:int?}', function($id = 1){
return "User id is $id";
});

// set name for route
$router->get('/users/{id}', 'UserController@index')->name('user_index');

// set prefix for route
$router->get('/users/{id}', 'UserController@index')->prefix('/api/v1');

// set middleware for route
$router->get('/users/{id}', 'UserController@index')->middleware([FooMiddleware::class, BarMiddleware::class]);

// route group
$router->group(function($router){
$router->get('/users', 'UserController@index');
$router->get('/users/{id}', 'UserController@show');
}, [FooMiddleware::class, BarMiddleware::class], '/api/v1');


```

## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file.


## License

[MIT](LICENSE.md).
Binary file added art/routail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "alirezasalehizadeh/routail",
"description": "A spidey PHP router.",
"keywords": ["php", "route", "router"],
"license": "MIT",
"authors": [
{
"name": "Alireza Salehizadeh",
"email": "[email protected]"
}
],
"require": {
"php": "^8.2.0"
},
"require-dev": {
"pestphp/pest": "^2.6.3"
},
"autoload": {
"psr-4": {
"AlirezaSalehizadeh\\Routail\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"AlirezaSalehizadeh\\Routail\\Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true,
"preferred-install": "dist",
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}
109 changes: 109 additions & 0 deletions src/Compiler/RoutePatternCompiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace AlirezaSalehizadeh\Routail\Compiler;

use AlirezaSalehizadeh\Routail\Route;
use AlirezaSalehizadeh\Routail\Enums\ParameterType;
use AlirezaSalehizadeh\Routail\Exceptions\InvalidParameterTypeException;


class RoutePatternCompiler
{

private string $pattern;

private array $types = [
'any' => '([^/]+)',
'id' => '\d+',
'int' => '\d+',
'string' => '[^/]+',
'uuid' => '([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})',
'slug' => '([\w\-_]+)',
'bool' => '(true|false|1|0)',
'date' => '([0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]))',
'int?' => '(?:/([0-9]+))?',
'any?' => '(?:/([a-zA-Z0-9\.\-_%=]+))?',
];

public function __construct(
private Route $route
) {
$this->pattern = $this->route->getPattern();
}

public function toRegex(): string
{
if ($this->hasParameter()) {
return $this->compilePatternWithParameter();
}

return $this->compilePatternWithoutParameter();
}

private function compilePatternWithoutParameter(): string
{
$this->pattern = str_replace('/', '\/', $this->pattern);
return '/^' . $this->pattern . '$/';
}

private function compilePatternWithParameter(): string
{
$parameters = $this->getParameters();

foreach ($parameters[1] as $parameter) {
if ($this->parameterHaveType($parameter)) {
[$key, $type] = $this->getParameterKeyAndType($parameter);
if ($this->parameterTypeIsValid($type)) {
$this->compilePatternWithParameterAndType($key, $type);
continue;
}
}
$this->compilePatternWithParameterWithoutType($parameter);
}
$this->pattern = str_replace('/', '\/', $this->pattern);
return '/^' . $this->pattern . '$/';
}

public function hasParameter(): bool
{
preg_match('/\{(.*?)\}/', $this->pattern, $matches);
return !empty($matches);
}

private function getParameters(): array
{
preg_match_all('/\{(.*?)\}/', $this->pattern, $matches);
return $matches;
}

private function parameterHaveType(string $parameter): bool
{
return (bool) preg_match('/\w+:\w+/', $parameter, $matches);
}

private function getParameterKeyAndType(string $parameter): array
{
return explode(':', $parameter);
}

private function compilePatternWithParameterWithoutType(string $key)
{
$this->pattern = preg_replace("/\{($key)\}/", '([^/]+)', $this->pattern);
}

private function compilePatternWithParameterAndType(string $key, string $type)
{
if (str_ends_with($type, '?')) {
$this->pattern = preg_replace("/\{($key):(\w+.)\}/", '.?' . $this->types[$type], $this->pattern);
return;
}
$this->pattern = preg_replace("/\{($key):(\w+)\}/", '(' . $this->types[$type] . ')', $this->pattern);
}

private function parameterTypeIsValid(string $type): bool
{
return (bool) ParameterType::find($type) ?: throw new InvalidParameterTypeException($type);
}
}
12 changes: 12 additions & 0 deletions src/Enums/HttpMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace AlirezaSalehizadeh\Routail\Enums;

enum HttpMethod: string
{
case GET = "GET";
case POST = "POST";
case PUT = "PUT";
case PATCH = "PATCH";
case DELETE = "DELETE";
}
22 changes: 22 additions & 0 deletions src/Enums/ParameterType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace AlirezaSalehizadeh\Routail\Enums;

enum ParameterType: string
{
case ANY = "any";
case ID = "id";
case INT = "int";
case STRING = "string";
case UUID = "uuid";
case BOOL = "bool";
case SLUG = "slug";
case DATE = "date";
case INT_NULLABLE = "int?";
case ANY_NULLABLE = "any?";

public static function find(string $parameterType)
{
return self::tryFrom(strtolower($parameterType));
}
}
Loading

0 comments on commit c949679

Please sign in to comment.