{"id":13407353,"url":"https://github.com/c9s/Pux","last_synced_at":"2025-03-14T11:31:18.389Z","repository":{"id":56958106,"uuid":"15615945","full_name":"c9s/Pux","owner":"c9s","description":"Pux is a fast PHP Router and includes out-of-box controller tools","archived":false,"fork":false,"pushed_at":"2023-03-28T09:46:00.000Z","size":1125,"stargazers_count":1272,"open_issues_count":23,"forks_count":81,"subscribers_count":81,"default_branch":"master","last_synced_at":"2024-10-29T15:34:10.720Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c9s.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2014-01-03T18:08:11.000Z","updated_at":"2024-10-29T14:30:46.000Z","dependencies_parsed_at":"2023-09-26T09:08:32.940Z","dependency_job_id":null,"html_url":"https://github.com/c9s/Pux","commit_stats":{"total_commits":908,"total_committers":12,"mean_commits":75.66666666666667,"dds":0.4041850220264317,"last_synced_commit":"30bc692db9eeb9b1937e9afa5d296c3ba7a54308"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c9s%2FPux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c9s%2FPux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c9s%2FPux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c9s%2FPux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c9s","download_url":"https://codeload.github.com/c9s/Pux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243188201,"owners_count":20250453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T20:00:38.339Z","updated_at":"2025-03-14T11:31:16.066Z","avatar_url":"https://github.com/c9s.png","language":"C","readme":"Pux\n=============\nPux is a faster PHP router, it also includes out-of-box controller helpers.\n\n[![Latest Stable Version](https://poser.pugx.org/corneltek/pux/v/stable)](https://packagist.org/packages/corneltek/pux) \n[![Total Downloads](https://poser.pugx.org/corneltek/pux/downloads)](https://packagist.org/packages/corneltek/pux) \n[![Latest Unstable Version](https://poser.pugx.org/corneltek/pux/v/unstable)](https://packagist.org/packages/corneltek/pux)\n[![License](https://poser.pugx.org/corneltek/pux/license)](https://packagist.org/packages/corneltek/pux)\n[![Monthly Downloads](https://poser.pugx.org/corneltek/pux/d/monthly)](https://packagist.org/packages/corneltek/pux)\n[![Daily Downloads](https://poser.pugx.org/corneltek/pux/d/daily)](https://packagist.org/packages/corneltek/pux)\n\n**2.0.x Branch Build Status** *(This branch is under development)*\n\nBenchmark\n--------------------\n- See \u003chttps://github.com/tyler-sommer/php-router-benchmark\u003e for details\n\n\nFEATURES\n--------------------\n\n- Low memory footprint (only 6KB with simple routes and extension installed) .\n- Low overhead.\n- PCRE pattern path support. (Sinatra-style syntax)\n- Controller auto-mounting - you mount a controller automatically without specifying paths for each action.\n- Controller annotation support - you may override the default path from controller through the annotations.\n- Route with optional pattern.\n- Request constraints\n  - Request method condition support.\n  - Domain condition support.\n  - HTTPS condition support.\n\nREQUIREMENT\n--------------\n\n- PHP 5.4+\n\n\nINSTALLATION\n--------------------\n\n```sh\ncomposer require corneltek/pux \"2.0.x-dev\"\n```\n\nSYNOPSIS\n--------\n\nThe routing usage is dead simple:\n\n```php\nrequire 'vendor/autoload.php'; // use PCRE patterns you need Pux\\PatternCompiler class.\nuse Pux\\RouteExecutor;\n\nclass ProductController {\n    public function listAction() {\n        return 'product list';\n    }\n    public function itemAction($id) { \n        return \"product $id\";\n    }\n}\n$mux = new Pux\\Mux;\n$mux-\u003eany('/product', ['ProductController','listAction']);\n$mux-\u003eget('/product/:id', ['ProductController','itemAction'] , [\n    'require' =\u003e [ 'id' =\u003e '\\d+', ],\n    'default' =\u003e [ 'id' =\u003e '1', ]\n]);\n$mux-\u003epost('/product/:id', ['ProductController','updateAction'] , [\n    'require' =\u003e [ 'id' =\u003e '\\d+', ],\n    'default' =\u003e [ 'id' =\u003e '1', ]\n]);\n$mux-\u003edelete('/product/:id', ['ProductController','deleteAction'] , [\n    'require' =\u003e [ 'id' =\u003e '\\d+', ],\n    'default' =\u003e [ 'id' =\u003e '1', ]\n]);\n\n// If you use ExpandableController, it will automatically expands your controller actions into a sub-mux\n$mux-\u003emount('/page', new PageController);\n\n$submux = new Pux\\Mux;\n$submux-\u003eany('/bar');\n$mux-\u003emount('/foo',$submux); // mount as /foo/bar\n\n// RESTful Mux Builder\n$builder = new RESTfulMuxBuilder($mux, [ 'prefix' =\u003e '/=' ]);\n$builder-\u003eaddResource('product', new ProductResourceController); // expand RESTful resource point at /=/product\n$mux = $builder-\u003ebuild();\n\n\nif ($route = $mux-\u003edispatch('/product/1')) {\n    $response = RouteExecutor::execute($route);\n\n    $responder = new Pux\\Responder\\SAPIResponder();\n    // $responder-\u003erespond([ 200, [ 'Content-Type: text/plain' ], 'Hello World' ]);\n    $responder-\u003erespond($response);\n}\n```\n\n\nMux\n---\nMux is where you define your routes, and you can mount multiple mux to a parent one.\n\n```php\n$mainMux = new Mux;\n\n$pageMux = new Mux;\n$pageMux-\u003eany('/page1', [ 'PageController', 'page1' ]);\n$pageMux-\u003eany('/page2', [ 'PageController', 'page2' ]);\n\n// short-hand syntax\n$pageMux-\u003eany('/page2', 'PageController:page2'  );\n\n$mainMux-\u003emount('/sub', $pageMux);\n\nforeach( ['/sub/page1', '/sub/page2'] as $p ) {\n    $route = $mainMux-\u003edispatch($p);\n\n    // The $route contains [ pcre (boolean), path (string), callback (callable), options (array) ]\n    list($pcre, $path, $callback, $options) = $route;\n}\n```\n\n### Methods\n\n- `Mux-\u003eadd( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003epost( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003eget( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003eput( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003eany( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003edelete( {path}, {callback array or callable object}, { route options })`\n- `Mux-\u003emount( {path}, {mux object}, { route options })`\n- `Mux-\u003elength()` returns length of routes.\n- `Mux-\u003eexport()` returns Mux constructor via __set_state static method in php code.\n- `Mux-\u003edispatch({path})` dispatch path and return matched route.\n- `Mux-\u003egetRoutes()` returns routes array.\n- `Mux::__set_state({object member array})` constructs and returns a Mux object.\n\n### Sorting routes\n\nYou need to sort routes when not using compiled routes, it's because pux sorts\nlonger path to front:\n\n```php\n$pageMux = new Mux;\n$pageMux-\u003eadd('/', [ 'PageController', 'page1' ]);\n$pageMux-\u003eadd('/pa', [ 'PageController', 'page1' ]);\n$pageMux-\u003eadd('/page', [ 'PageController', 'page1' ]);\n$pageMux-\u003esort();\n```\n\nThis sorts routes to:\n\n```\n/page\n/pa\n/\n```\n\nSo pux first compares `/page`, `/pa`, than `/`.\n\n### Different String Comparison Strategies\n\nWhen expand is enabled, the pattern comparison strategy for \nstrings will match the full string.\n\nWhen expand is disabled, the pattern comparison strategy for \nstrings will match the prefix.\n\n\nRouteRequest\n-------------------------\n\nRouteRequest maintains the information of the current request environment, it\nalso provides some constraint checking methods that helps you to identify a\nrequest, e.g.:\n\n```php\nif ($request-\u003equeryStringMatch(...)) {\n\n}\nif ($request-\u003ehostEqual('some.dev')) {\n\n}\nif ($request-\u003epathEqual('/foo/bar')) {\n\n}\n```\n\n\n```php\nuse Pux\\Environment;\n$env = Environment::createFromGlobals();\n$request = RouteRequest::createFromEnv($env);\n\nif ($route = $mux-\u003edispatchRequest($request)) {\n\n}\n```\n\n\nAPCDispatcher\n----------------------\nAlthough Pux\\\\Mux is already fast, you can still add APCDispatcher to boost the\nperformance, which is to avoid re-lookup route.\n\nThis is pretty useful when you have a lot of PCRE routes.\n\n```\nuse Pux\\Dispatcher\\APCDispatcher;\n$dispatcher = new APCDispatcher($mux, array(\n    'namespace' =\u003e 'app_',\n    'expiry' =\u003e ...,\n));\n$route = $dispatcher-\u003edispatch('/request/uri');\nvar_dump($route);\n```\n\nController\n--------------------\n\nPux provides the ability to map your controller methods to paths automatically, done either through a simple, fast controller in the C extension or its pure PHP counterpart:\n\n```php\nclass ProductController extends \\Pux\\Controller\n{\n    // translate to path \"\"\n    public function indexAction() { }\n\n    // translate to path \"/add\"\n    public function addAction() { }\n\n    // translate to path \"/del\"\n    public function delAction() { }\n}\n\n$mux = new Pux\\Mux;\n$submux = $controller-\u003eexpand();\n$mux-\u003emount( '/product' , $submux );\n\n// or even simpler\n$mux-\u003emount( '/product' , $controller);\n\n$mux-\u003edispatch('/product');       // ProductController-\u003eindexAction\n$mux-\u003edispatch('/product/add');   // ProductController-\u003eaddAction\n$mux-\u003edispatch('/product/del');   // ProductController-\u003edelAction\n```\n\nYou can also use `@Route` and `@Method` annotations to override the default `\\Pux\\Controller::expand()` functionality:\n\n```php\nclass ProductController extends \\Pux\\Controller\n{\n    /**\n     * @Route(\"/all\")\n     * @Method(\"GET\")\n     */\n    public function indexAction() {\n        // now available via GET /all only\n    }\n    \n    /**\n     * @Route(\"/create\")\n     * @Method(\"POST\")\n     */\n    public function addAction() {\n        // now available via POST /create only\n    }\n    \n    /**\n     * @Route(\"/destroy\")\n     * @Method(\"DELETE\")\n     */\n    public function delAction() {\n        // now available via DELETE /destroy only\n    }\n}\n```\n\nThis is especially helpful when you want to provide more specific or semantic\n(e.g., HTTP method-specific) actions.  Note that by default, expanded\ncontroller routes will be available via any HTTP method - specifying `@Method`\nwill restrict it to the provided method.\n\n- `Pux\\Controller::expand()` returns an instance of `\\Pux\\Mux` that contains\n  the controller's methods mapped to URIs, intended to be mounted as a sub mux\n  in another instance of `\\Pux\\Mux`.\n\n\nRoute RouteExecutor\n--------------------\n`Pux\\RouteExecutor` executes your route by creating the controller object, and\ncalling the controller action method.\n\nRoute executor take the returned route as its parameter, you simply pass the\nroute to executor the controller and get the execution result.\n\nHere the simplest example of the usage:\n\n```php\nuse Pux\\RouteExecutor;\n$mux = new Pux\\Mux;\n$mux-\u003eany('/product/:id', ['ProductController','itemAction']);\n$route = $mux-\u003edispatch('/product/1');\n$result = RouteExecutor::execute($route);\n```\n\nYou can also define the arguments to the controller's constructor method:\n\n```php\n\nclass ProductController extends Pux\\Controller {\n    public function __construct($param1, $param2) {\n        // do something you want\n    }\n    public function itemAction($id) {\n        return \"Product $id\";\n    }\n}\n\nuse Pux\\RouteExecutor;\n$mux = new Pux\\Mux;\n$mux-\u003eany('/product/:id', ['ProductController','itemAction'], [ \n    'constructor_args' =\u003e [ 'param1', 'param2' ],\n]);\n$route = $mux-\u003edispatch('/product/1');\n$result = RouteExecutor::execute($route); // returns \"Product 1\"\n```\n\nDispatching Strategy\n--------------------\n\nThere are two route dispatching strategies in Pux while Symfony/Routing only\nprovides PCRE pattern matching:\n\n1. Plain string comparison.\n2. PCRE pattern comparison.\n\nYou've already knew that PCRE pattern matching is slower than plain string comparison, although PHP PCRE caches the compiled patterns.\n\nThe plain string comparison is designed for static routing paths, it\nimproves the performance while you have a lot of simple routes.\n\nThe PCRE pattern comparison is used when you have some dynamic routing paths,\nfor example, you can put some place holders in your routing path, and pass\nthese path arguments to your controller later.\n\nPux sorts and compiles your routes to single cache file, it also uses longest\nmatching so it sorts patterns by pattern length in descending order before compiling the\nroutes to cache.\n\nPux uses indexed array as the data structure for storing route information so it's faster.\n\n\nRouting Path Format\n---------------------\n\nStatic route:\n\n    /post\n\nPCRE route:\n\n    /post/:id                  =\u003e matches /post/33\n\nPCRE route with optional pattern:\n\n    /post/:id(/:title)         =\u003e matches /post/33, /post/33/post%20title\n    /post/:id(\\.:format)       =\u003e matches /post/33, /post/33.json .. /post/33.xml\n\n\n\n\n## Q \u0026 A\n\n### Why It's Faster\n\n- Pux uses simpler data structure (indexed array) to store the patterns and flags.\n    (In PHP internals, `zend_hash_index_find` is faster than `zend_hash_find`).\n\n- When matching routes, symfony uses a lot of function calls for each route:\n\n    https://github.com/symfony/Routing/blob/master/Matcher/UrlMatcher.php#L124\n\n    Pux fetches the pattern from an indexed-array:\n\n    https://github.com/c9s/Pux/blob/master/src/Pux/Mux.php#L189\n\n- Even you enabled APC or other bytecode cache extension, you are still calling\n  methods and functions in the runtime. Pux reduces the route building to one\n  static method call. `__set_state`.\n\n- Pux separates static routes and dynamic routes automatically, Pux uses hash\n  table to look up static routes without looping the whole route array.\n\n- Pux\\\\Mux is written in C extension, method calls are faster!\n\n- With C extension, there is no class loading overhead.\n\n- Pux compiles your routes to plain PHP array, the compiled routes can be\n  loaded very fast. you don't need to call functions to register your routes before using it.\n\n\n### Why It's Here\n\nMost of us use a lot of machines to run our applications, however, it uses too\nmuch energy and too many resources.\n\nSome people thinks routing is not the bottleneck, the truth is this project\ndoes not claim routing is the bottleneck.\n\nActually the \"bottleneck\" is always different in different applications, if you\nhave a lot of heavy db requests, then your bottleneck is your db; if you have a\nlot of complex computation, then the bottleneck should be your algorithm.\n\nYou might start wondering since the bottleneck is not routing, why do we\nimplement route dispatcher in C extension? The answer is simple, if you put a\npure PHP routing component with some empty callbacks and use apache benchmark\ntool to see how many requests you can handle per second, you will find out the\nrouting component consumes a lot of computation time and the request number\nwill decrease quite a few. (and it does nothing, all it does is ... just\nrouting)\n\nPux tries to reduce the overheads of loading PHP classes and the runtime\nmethod/function calls, and you can run your application faster without the\noverheads.\n\n### Pros \u0026 Cons of Grouped Pattern Matching Strategy\n\nAn idea of matching routes is to combine all patterns into one pattern and\ncompare the given path with `pcre_match` in one time.\n\nHowever this approach does not work if you have optional group or named\ncapturing group, the `pcre_match` can not return detailed information about\nwhat pattern is matched if you use one of them.\n\nAnd since you compile all patterns into one, you can't compare with other same\npatterns with different conditions, for example:\n\n    /users  # GET\n    /users  # POST\n    /users  # with HTTP_HOST=somedomain\n\nThe trade off in Pux is to compare routes in sequence because the same pattern\nmight be in different HTTP method or different host name.\n\nThe best approach is to merge \u0026 compile the regexp patterns into a FSM (Finite\nstate machine), complex conditions can also be merged into this FSM, and let\nthis FSM to dispatch routes. And this is the long-term target of Pux.\n\n\n\n\n\n\n## Contributing\n\n### Testing XHProf Middleware\n\n\nDefine your XHPROF_ROOT in your `phpunit.xml`, you can copy `phpunit.xml.dist` to `phpunit.xml`,\nfor example:\n\n```xml\n  \u003cphp\u003e\n    \u003cenv name=\"XHPROF_ROOT\" value=\"/Users/c9s/src/php/xhprof\"/\u003e\n  \u003c/php\u003e\n```\n\n\n\n\n### Hacking Pux C extension\n\n1. Discuss your main idea on GitHub issue page.\n\n2. Fork this project and open a branch for your hack.\n\n3. Development Cycle:\n\n        cd ext\n        ./compile\n        ... hack hack hack ...\n\n        # compile and run phpunit test\n        ./compile \u0026\u0026 ./test -- --debug tests/Pux/MuxTest.php\n\n        # use lldb to debug extension code\n        ./compile \u0026\u0026 ./test -l -- tests/Pux/MuxTest.php\n\n        # use gdb to debug extension code\n        ./compile \u0026\u0026 ./test -g -- tests/Pux/MuxTest.php\n\n4. Commit!\n\n5. Send pull request and describe what you've done and what is changed.\n\n","funding_links":[],"categories":["Lib/Framework/API/Dev Tool","微型框架","路由 Routers","C","Table of Contents","PHP","Micro Frameworks","目录","路由( Routers )"],"sub_categories":["Routers","路由 Routers"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc9s%2FPux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc9s%2FPux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc9s%2FPux/lists"}