Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
ozdemirrulass committed May 12, 2024
0 parents commit 770644e
Show file tree
Hide file tree
Showing 22 changed files with 3,124 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: MAIN

on:
push:
branches:
- main

jobs:
tests-php8:
runs-on: ubuntu-20.04

strategy:
fail-fast: true
matrix:
php-versions: ['8.1', '8.2', '8.3']

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}

- name: Install dependencies
run: composer require -W phpunit/phpunit

- name: Run tests
run: composer run-script test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor
composer.lock
.idea
208 changes: 208 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# PHP URI Template

This is a URI Template implementation in PHP based on [RFC 6570 URI Template](http://tools.ietf.org/html/rfc6570). In addition to URI expansion, it also supports URI extraction.

* This repository is strictly typed, cleaner version of [rize/UriTemplate](https://github.com/rize/UriTemplate)
* To ensure that the package functions properly, tests are duplicated exactly as they are.

## Installation

Installation via `composer`:

```
composer require ozdemirrulass/uri-template
```


## Usage

### Expansion

A very simple usage (string expansion).

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();
$uri->expand('/{username}/profile', ['username' => 'john']);

>> '/john/profile'
```

`Ozdemirrulass\UriTemplate` supports all `Expression Types` and `Levels` specified by RFC6570.

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();
$uri->expand('/search/{term:1}/{term}/{?q*,limit}', [
'term' => 'john',
'q' => ['a', 'b'],
'limit' => 10,
])

>> '/search/j/john/?q=a&q=b&limit=10'
```

#### `/` Path segment expansion

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();
$uri->expand('http://{host}{/segments*}/{file}{.extensions*}', [
'host' => 'www.host.com',
'segments' => ['path', 'to', 'a'],
'file' => 'file',
'extensions' => ['x', 'y'],
]);

>> 'http://www.host.com/path/to/a/file.x.y'
```

`Ozdemirrulass\UriTemplate` accepts `base-uri` as a 1st argument and `default params` as a 2nd argument. This is very useful when you're working with API endpoint.

Take a look at real world example.

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate('https://api.twitter.com/{version}', ['version' => 1.1]);
$uri->expand('/statuses/show/{id}.json', ['id' => '210462857140252672']);

>> https://api.twitter.com/1.1/statuses/show/210462857140252672.json
```

### Extraction

It also supports URI Extraction (extract all variables from URI). Let's take a look at the example.

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate('https://api.twitter.com/{version}', ['version' => 1.1]);

$params = $uri->extract('/search/{term:1}/{term}/{?q*,limit}', '/search/j/john/?q=a&q=b&limit=10');

>> print_r($params);
(
[term:1] => j
[term] => john
[q] => Array
(
[0] => a
[1] => b
)

[limit] => 10
)
```

Note that in the example above, result returned by `extract` method has an extra keys named `term:1` for `prefix` modifier. This key was added just for our convenience to access prefix data.

#### `strict` mode

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();
$uri->extract($template, $uri, $strict = false)
```

Normally `extract` method will try to extract vars from a uri even if it's partially matched. For example

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();
$params = $uri->extract('/{?a,b}', '/?a=1')

>> print_r($params);
(
[a] => 1
[b] => null
)
```

With `strict mode`, it will allow you to extract uri only when variables in template are fully matched with given uri.

Which is useful when you want to determine whether the given uri is matched against your template or not (in case you want to use it as routing service).

```php
<?php

use Ozdemirrulass\UriTemplate;

$uri = new UriTemplate();

// Note that variable `b` is absent in uri
$params = $uri->extract('/{?a,b}', '/?a=1', true);

>>> null

// Now we give `b` some value
$params = $uri->extract('/{?a,b}', '/?a=1&b=2', true);

>>> print_r($params)
(
[a] => 1
[b] => 2
)
```

#### Array modifier `%`

By default, RFC 6570 only has 2 types of operators `:` and `*`. This `%` array operator was added to the library because current spec can't handle array style query e.g. `list[]=a` or `key[user]=john`.

Example usage for `%` modifier

```php
<?php

$uri->expand('{?list%,keys%}', [
'list' => [
'a', 'b',
),
'keys' => [
'a' => 1,
'b' => 2,
),
]);

// '?list[]=a&list[]=b&keys[a]=1&keys[b]=2'
>> '?list%5B%5D=a&list%5B%5D=b&keys%5Ba%5D=1&keys%5Bb%5D=2'

// [] get encoded to %5B%5D i.e. '?list[]=a&list[]=b&keys[a]=1&keys[b]=2'
$params = $uri->extract('{?list%,keys%}', '?list%5B%5D=a&list%5B%5D=b&keys%5Ba%5D=1&keys%5Bb%5D=2', )

>> print_r($params);
(
[list] => Array
(
[0] => a
[1] => b
)

[keys] => Array
(
[a] => 1
[b] => 2
)
)
```


28 changes: 28 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "ozdemirrulass/uri-template",
"description": "Enhanced PHP URI Template (RFC 6570) supports both expansion & extraction",
"keywords": ["URI", "Template", "RFC 6570"],
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Ozdemirrulass\\": "src/Ozdemirrulass/"
}
},
"authors": [
{
"name": "ozdemirrulass",
"email": "[email protected]"
}
],
"scripts": {
"test": "vendor/bin/phpunit test/"
},
"minimum-stability": "stable",
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^11.2@dev"
}
}
61 changes: 61 additions & 0 deletions src/Ozdemirrulass/UriTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Ozdemirrulass;

use Ozdemirrulass\UriTemplate\Parser;

class UriTemplate
{
public function __construct(
protected string $base_uri = '',
protected array $params = [],
protected Parser $parser = new Parser()
) {
}

public function expand(string $uri, array $params = array()): string
{
$params += $this->params;
$uri = $this->base_uri.$uri;
$result = array();

if ((strpos($uri, '{')) === false) {
return $uri;
}

$parser = $this->parser;
$nodes = $parser->parse($uri);

foreach ($nodes as $node) {
$result[] = $node->expand($params);
}

return implode('', $result);
}

public function extract(string $template, string $uri, bool $strict = false)
{
$params = array();
$nodes = $this->parser->parse($template);

foreach ($nodes as $node) {
if ($strict && !strlen((string)$uri)) {
return null;
}

$match = $node->match($this->parser, $uri, $params, $strict);

list($uri, $params) = $match;
}

if ($strict && strlen((string)$uri)) {
return null;
}

return $params;
}


}
36 changes: 36 additions & 0 deletions src/Ozdemirrulass/UriTemplate/Node/Abstraction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Ozdemirrulass\UriTemplate\Node;

use Ozdemirrulass\UriTemplate\Parser;

abstract class Abstraction
{
public function __construct(private readonly string $token)
{
}

public function expand(array $params = array()): ?string
{
return $this->token;
}

public function match(Parser $parser, string $uri, array $params = array(), bool $strict = false): ?array
{
$length = strlen($this->token);
if (substr($uri, 0, $length) === $this->token) {
$uri = substr($uri, $length);
} elseif ($strict) {
return null;
}

return array($uri, $params);
}

public function getToken(): string
{
return $this->token;
}
}
Loading

0 comments on commit 770644e

Please sign in to comment.