2017 © Pedro PelĂĄez
 

library simple-routing

Lightweight and flexible request router framework

image

pinepain/simple-routing

Lightweight and flexible request router framework

  • Wednesday, April 25, 2018
  • by pinepain
  • Repository
  • 2 Watchers
  • 12 Stars
  • 23 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 2 Forks
  • 0 Open issues
  • 5 Versions
  • 5 % Grown

The README.md

Php Simple Router

Build Status, (*1)

About:

This is yet another HTTP request routing implementation which build with simplicity and flexibility in mind. In fact it is set of tools to build your own HTTP routing stack for your specific needs., (*2)

In this project I compiled best known experience in routing by combining simplicity, flexibility and speed. Special thanks to Nikita Popov an his FastRoute routing engine implementation and his perfect blog post explaining how the implementation works and why it is fast., (*3)

I hope we can merge my features to his implementation, but as FastRoute is experimental (at some parts) stuff and tries to do full routing cycle (playing with HTTP request method, which I prefer let to end-user) it may take a while., (*4)

There is bench.php file which gives you a brief numbers how fast this implementation to other implementations. You can run composer require "nikic/fast-route : *@dev" && php bench.php to compare with FastRoute implementation which is the one of the fastest. Some performance degradation (about 10%) probably is the result of extra code related to extended parameters support (optional parameters, default values, etc.)., (*5)

Features:

  • best practices from routing experience
  • full unicode support
  • optional parameters
  • custom types
  • flexibility (yeah, someone may say that it is so flexible that you have to give it a support)
  • lose coupled code (at least wannabe)
  • speed (one of fastest from all what you've ever seen, marketing guys may add "close to bare metal", but that is not true as all we know)

Example usage:

    <?php

    $loader = require __DIR__ . "/vendor/autoload.php";

    use Pinepain\SimpleRouting\Solutions\SimpleRouter;
    use Pinepain\SimpleRouting\Parser;
    use Pinepain\SimpleRouting\RoutesCollector;
    use Pinepain\SimpleRouting\Compiler;
    use Pinepain\SimpleRouting\Filter;
    use Pinepain\SimpleRouting\CompilerFilters\Formats;
    use Pinepain\SimpleRouting\CompilerFilters\Helpers\FormatsCollection;
    use Pinepain\SimpleRouting\RulesGenerator;
    use Pinepain\SimpleRouting\Matcher;
    use Pinepain\SimpleRouting\FormatsHandler;
    use Pinepain\SimpleRouting\FormatHandlers\Path as PathFormatHandler;
    use Pinepain\SimpleRouting\UrlGenerator;

    $formats_preset = [
        ['segment', '[^/]+', ['default']],
        ['alpha', '[[:alpha:]]+', ['a']],
        ['digit', '[[:digit:]]+', ['d']],
        ['word', '[\w]+', ['w']],
        // see http://stackoverflow.com/questions/19256323/regex-to-match-a-slug
        ['slug', '[a-z0-9]+(?:-[a-z0-9]+)*', ['s']],
        ['path', '.+', 'p'],
    ];

    $collector       = new RoutesCollector(new Parser());
    $filter          = new Filter([new Formats(new FormatsCollection($formats_preset))]);
    $generator       = new RulesGenerator($filter, new Compiler());
    $dispatcher      = new Matcher();
    $formats_handler = new FormatsHandler([new PathFormatHandler()]);
    $url_generator   = new UrlGenerator($formats_handler);

    $router = new SimpleRouter($collector, $generator, $dispatcher, $url_generator);

    $router->add('/some/{path}', 'handler');

    $url = '/some/homepage';


    function handler($homepage) {
        var_dump(func_get_args());
    }

    $result = $router->match($url);

    call_user_func_array($result->handler, $result->variables);

Note: While this framework is rather set of blocks it was built with mind and hope that you will use some IoC container, but you can do all that manually., (*6)

Urls generation:

In basic implementation we use handler value for routes identification. Same handler value for multiple routes will lead to situation, when url will be generated based on last added route., (*7)

Example:

For example, we have SimpleRouter initialized as written in example above, then, populate it with extra routes:, (*8)


$router->add('/first/route/{with_param}', 'handler1'); $router->add('/second/{route}/{foo}/{bar}', 'handler2'); $router->add('/route/{with}{/optional?}{/param?default}', 'handler3');

now we can generate some urls:, (*9)


echo $router->url('handler1', ['with_param' => 'param-value']), PHP_EOL; // gives us /first/route/param-value echo $router->url('handler2', ['route' => 'example', 'foo' =>'val', 'bar' => 'given']), PHP_EOL; // gives us /second/example/val/given echo $router->url('handler3', ['with' => 'some', 'optional' =>'given']), PHP_EOL; // gives us /route/some/given // When we skip parameters with default value, optinal parameter and the reset of path not included in generated url: echo $router->url('handler3', ['with' => 'some']), PHP_EOL; // gives us /route/some // we can force default params insertion: echo $router->url('handler3', ['with' => 'some', 'optional' => 'without-default'], true), PHP_EOL; // gives us /route/some/without-default // this one fill fail while we didn't set default value for optional parameter, but asked to generate full url //echo $router->url('handler3', ['with' => 'some'], true), PHP_EOL; // gives us /route/some/default

By default route params limited to single segment (placed between slashes ('/')), so by default raw slash is not permitted in parameter value and goes encoded. As well as any non-url-safe characters. To change that behavior, you can define custom types handlers which will form param value as needed., (*10)

Empty values for parameter values are not permitted., (*11)

Rules syntax:

Route parameters:

You can capture segments of the request URI within your route with named parameters. Parameters description are written within curl brackets ({ and }) and consist from parameter name, parameter default value and parameter type., (*12)

Nested parameters are not supported, use groups instead., (*13)

Parameter name:

Valid parameter name should start from letter or underscore and can contain alphanumeric, underscore and dash characters, no spaces allowed. It can be described with regexp: [a-zA_Z_][a-zA_Z0-9_-]*., (*14)

These are valid parameter names:, (*15)

  • {valid_parameter}
  • {valid-parameter}
  • {alsoValid}
  • {_foo}
  • {_}
  • {bar_}
  • {bar123}

And these are not:, (*16)

  • {-i-am-invalid}
  • {1slug}
  • {}
  • {no spaces allowed}
  • {önly_alphĂ€nĂŒmeric_allowed}

Note, that leading underscore in parameter name lead under certain circumstances in user-land code to intersection with PHP magic methods. Though it is less likely, you've being warned., (*17)

Optional parameters and default values:

By default all parameters are mandatory. To mark route parameter is optional add question mark (?) after it name with parameter default value:, (*18)

{i_am_optional?missed}

Note, that default value is also optional and if not given, null will be used instead:, (*19)

{i_am_optional_and_null_by_default?}

It is often desired to have optional parameter separator, for example, slash (/), optional too. To do so, you can embed any punctuation character, except curly brackets ({, }), colon (:), percent (%) and question mark (?) (anyway, question mark is not a part of valid URL path)., (*20)

  • /some/route{/optional?} will match /some/route/value and /some/route
  • /some/route/{optional/?} will match /some/route/value/ and /some/route/

If you follow trailing slash path convention (e.g. you always require trailing slash), than you can specify it after parameter name:, (*21)

  • /some/route/{optional?} will match /some/route/value and /some/route/, but not /some/route or /some/route/value/.
  • /some/route/{optional/?} will match /some/route/value/ and /some/route/, but not /some/route or /some/route/value.

Multiple params

Everything after optional parameter, will be optional too. Mandatory parameters after optional become mandatory only if optional parameter given. When mandatory parameter given after optional and optional one not given, mandatory parameter value will be set to default one - to null. Tip: it may be a good idea to keep optional all parameters after first optional one:, (*22)

  • /static{/optional?}/{mandatory} on path /static will lead to optional=null and mandatory=null
  • /static{/optional?}/{mandatory} on path /static/some-value will fail while it is expected to have slash and value for mandatory parameter (something like /mandatory-value)
  • /static{/optional?}/{mandatory} on path /static/some-value/mandatory-value will succeeds with optional=some-value and mandatory=mandatory-value
  • /static{/optional?}{/optional_too?} on path /static/some-value will lead to optional=some-value and optional_too=null and on path /static/some-value/more-values will lead to optional=some-value and optional_too=more-values

Parameter type:

By default parameter is whole URI segment (part between slashes):, (*23)

  • /this-is-segment/i-am-also-segment/
  • /123/numbers_are_segments_too
  • /ă‚»ă‚°ăƒĄăƒłăƒˆ/unicode-is-ok

You can limit that by specifying parameter type by placing colon (:) and type definition after parameter name. If parameter optional mark present (question mark ?) and no default value specified, colon is optional., (*24)

If parameter segment present but doesn't pass under type format, whole rule will no match., (*25)

Here is some examples:, (*26)

  • {parameter:\d+}
  • {parameter?:\w{2}-\d+}, this one meand that parameter is optional and has type regexp \w{2}-\d+.

Note, that parameter type regex must be valid and must not contains capturing groups., (*27)

Custom parameter type:

Custom type format regexp may be injected in PHP code:, (*28)

    use \Pinepain\SimpleRouting\CompilerFilters\Helpers\FormatsCollection;

    $formats_preset = [
        ['segment', '[^/]+', ['default']],
        ['alpha', '[[:alpha:]]+', ['a']],
        ['digit', '[[:digit:]]+', ['d']],
        ['word', '[\w]+', ['w']],
        // see http://stackoverflow.com/questions/19256323/regex-to-match-a-slug
        ['slug', '[a-z0-9]+(?:-[a-z0-9]+)*', ['s']],
        ['path', '.+', 'p'],
    ];

    $formats = FormatsCollection($formats_preset); 

So that later you can use something like:, (*29)

  • {parameter:digit}
  • {parameter:d}, (assume d is short for digit)
  • {parameter?:d}, this one mean that parameter is optional and has type digit.

You can also manually populate formats:, (*30)

    $formats->add('dmy_date', '\d{2}-\d{2}-\d{4}', ['dmy']);

and later use one of that definition as /segment/{parameter:dmy_date} or /segment/{parameter:dmy}., (*31)

To make all that happens you have to pass formats collection to Formats filter and later specify that filter to be applied to urls:, (*32)

    use \Pinepain\SimpleRouting\CompilerFilters\Formats;
    use \Pinepain\SimpleRouting\Filter;

    $formats_filter = new Formats($formats);
    $filter = new Filter([$formats_filter]);

    // and then pass it to rules data generator,

    $generator  = new RulesGenerator($filter, new Compiler());

    $router = new SimpleRouter($collector, $generator, $dispatcher);

    // full example shown above

Alternatively, you can in-line type definition into rule:, (*33)

/segment/{parameter:\d{2}-\d{2}-\d{4}} 

Leading and trailing slashes

Routing library makes no assumption which convention does end-user use nor enforce any convention. Removing or appending trailing slashes, if any, is not a scope of this library and thus may be enforced before rules adding, url matching or on generated url., (*34)

There is \Pinepain\SimpleRouting\Solutions\AdvancedRouter class which allows you to specify trailing slash policy for rules, matched and generated urls:, (*35)

<?php
    use Pinepain\SimpleRouting\Solutions\AdvancedRouter;

    $advanced_route = new AdvancedRouter($router, AdvancedRouter::ENFORCE_TRAILING_SLASH);

    // now trailing slash will be enforced for any rule and url which come in or out of $advanced_router

give a look at AdvancedRouter constants to see how you granular trailing slashes policy could be., (*36)

The Versions

25/04 2018

dev-master

9999999-dev https://github.com/pinepain/php-simple-routing

Lightweight and flexible request router framework

  Sources   Download

MIT

The Requires

  • php ^7.0

 

The Development Requires

micro framework lightweight request routing

24/04 2017

dev-stricter-types

dev-stricter-types https://github.com/pinepain/php-simple-routing

Lightweight and flexible request router framework

  Sources   Download

MIT

The Requires

  • php ^7.0

 

The Development Requires

micro framework lightweight request routing

25/03 2017

v0.3.0

0.3.0.0 https://github.com/pinepain/php-simple-routing

Lightweight and flexible request router framework

  Sources   Download

MIT

The Requires

  • php ^7.0

 

The Development Requires

micro framework lightweight request routing

18/02 2015

v0.2.0

0.2.0.0 https://github.com/pinepain/php-simple-routing

Lightweight and flexible request router framework

  Sources   Download

MIT

The Requires

  • php >=5.4.0

 

The Development Requires

micro framework lightweight request routing

14/02 2015

v0.1.0

0.1.0.0 https://github.com/pinepain/php-simple-routing

Lightweight and flexible request router framework

  Sources   Download

MIT

The Requires

  • php >=5.4.0

 

The Development Requires

micro framework lightweight request routing