├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .php_cs
├── .scrutinizer.yml
├── LICENSE
├── README.md
├── composer.json
├── docs
├── di.md
├── getting_started.md
├── handlers
│ ├── cli_handler.md
│ ├── controller_handler.md
│ ├── direct_handler.md
│ ├── index.md
│ ├── jsonrpc_handler.md
│ ├── pattern_handler.md
│ └── rest_handler.md
├── index.md
└── plugins
│ └── index.md
├── doxygen.conf
├── mkdocs.yml
├── phpunit.xml
├── src
└── Vectorface
│ └── SnappyRouter
│ ├── Authentication
│ ├── AbstractAuthenticator.php
│ ├── AuthenticatorInterface.php
│ └── CallbackAuthenticator.php
│ ├── Config
│ ├── Config.php
│ └── ConfigInterface.php
│ ├── Controller
│ └── AbstractController.php
│ ├── Di
│ ├── Di.php
│ ├── DiInterface.php
│ ├── DiProviderInterface.php
│ └── ServiceProvider.php
│ ├── Encoder
│ ├── AbstractEncoder.php
│ ├── EncoderInterface.php
│ ├── JsonEncoder.php
│ ├── JsonpEncoder.php
│ ├── NullEncoder.php
│ └── TwigViewEncoder.php
│ ├── Exception
│ ├── AccessDeniedException.php
│ ├── EncoderException.php
│ ├── HandlerException.php
│ ├── InternalErrorException.php
│ ├── MethodNotAllowedException.php
│ ├── PluginException.php
│ ├── ResourceNotFoundException.php
│ ├── RouterExceptionInterface.php
│ └── UnauthorizedException.php
│ ├── Handler
│ ├── AbstractCliHandler.php
│ ├── AbstractHandler.php
│ ├── AbstractRequestHandler.php
│ ├── BatchRequestHandlerInterface.php
│ ├── CliTaskHandler.php
│ ├── ControllerHandler.php
│ ├── DirectScriptHandler.php
│ ├── JsonRpcHandler.php
│ ├── PatternMatchHandler.php
│ └── RestHandler.php
│ ├── Plugin
│ ├── AbstractControllerPlugin.php
│ ├── AbstractPlugin.php
│ ├── AccessControl
│ │ └── CrossOriginRequestPlugin.php
│ ├── Authentication
│ │ ├── AbstractAuthenticationPlugin.php
│ │ └── HttpBasicAuthenticationPlugin.php
│ ├── ControllerPluginInterface.php
│ ├── HttpHeader
│ │ └── RouterHeaderPlugin.php
│ └── PluginInterface.php
│ ├── Request
│ ├── AbstractRequest.php
│ ├── HttpRequest.php
│ ├── HttpRequestInterface.php
│ ├── JsonRpcRequest.php
│ └── RequestInterface.php
│ ├── Response
│ ├── AbstractResponse.php
│ ├── JsonRpcResponse.php
│ ├── Response.php
│ └── ResponseInterface.php
│ ├── SnappyRouter.php
│ └── Task
│ ├── AbstractTask.php
│ └── TaskInterface.php
└── tests
├── Vectorface
└── SnappyRouterTests
│ ├── Authentication
│ └── CallbackAuthenticatorTest.php
│ ├── Config
│ └── ConfigTest.php
│ ├── Controller
│ ├── NonNamespacedController.php
│ ├── TestDummyController.php
│ └── Views
│ │ └── test
│ │ ├── array.twig
│ │ └── default.twig
│ ├── Di
│ ├── DiTest.php
│ └── ServiceProviderTest.php
│ ├── Encoder
│ ├── AbstractEncoderTest.php
│ ├── JsonEncoderTest.php
│ ├── JsonpEncoderTest.php
│ └── NullEncoderTest.php
│ ├── Exception
│ ├── InternalErrorExceptionTest.php
│ └── MethodNotAllowedExceptionTest.php
│ ├── Handler
│ ├── ControllerHandlerTest.php
│ ├── DirectScriptHandlerTest.php
│ ├── JsonRpcHandlerTest.php
│ ├── PatternMatchHandlerTest.php
│ ├── RestHandlerTest.php
│ └── test_script.php
│ ├── Plugin
│ ├── AccessControl
│ │ └── CrossOriginRequestPluginTest.php
│ ├── Authentication
│ │ ├── AbstractAuthenticationPluginTest.php
│ │ ├── HttpBasicAuthenticationPluginTest.php
│ │ └── TestAuthenticationPlugin.php
│ ├── HttpHeader
│ │ └── RouterHeaderPluginTest.php
│ ├── TestPlugin.php
│ └── TestPluginTest.php
│ ├── Request
│ ├── HttpRequestTest.php
│ └── JsonRpcRequestTest.php
│ ├── Response
│ └── JsonRpcResponseTest.php
│ ├── SnappyRouterTest.php
│ └── Task
│ ├── CliTaskHandlerTest.php
│ └── DummyTestTask.php
└── bootstrap.php
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | test:
13 | name: PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}
14 | runs-on: ${{ matrix.operating-system }}
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | operating-system: [ubuntu-latest]
19 | php-versions: ['8.0', '8.1', '8.2', '8.3']
20 |
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 |
25 | - name: Setup PHP, with composer and extensions
26 | uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
27 | with:
28 | php-version: ${{ matrix.php-versions }}
29 | coverage: pcov
30 | tools: composer:v2
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Get composer cache directory
35 | id: composer-cache
36 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
37 |
38 | - name: Setup problem matchers
39 | run: |
40 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
41 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
42 |
43 | - name: Cache composer dependencies
44 | uses: actions/cache@v3
45 | with:
46 | path: ${{ steps.composer-cache.outputs.dir }}
47 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
48 | restore-keys: ${{ runner.os }}-composer-
49 |
50 | - name: Install Composer dependencies
51 | run: composer install --prefer-dist
52 |
53 | - name: Test with phpunit
54 | run: composer test
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .php_cs.cache
3 | composer.lock
4 | composer.phar
5 | coverage/
6 | site/
7 | vendor/
8 | .phpunit.result.cache
9 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | true,
4 | // additional rules
5 | 'array_syntax' => ['syntax' => 'short'],
6 | 'binary_operator_spaces' => [
7 | 'default' => 'single_space',
8 | 'operators' => [
9 | '=>' => 'align_single_space_minimal',
10 | ],
11 | ],
12 | 'cast_spaces' => false,
13 | 'combine_consecutive_issets' => true,
14 | 'function_declaration' => ['closure_function_spacing' => 'none'],
15 | 'function_typehint_space' => true,
16 | 'hash_to_slash_comment' => true,
17 | 'include' => true,
18 | 'method_chaining_indentation' => true,
19 | 'no_blank_lines_after_class_opening' => true,
20 | 'no_closing_tag' => true,
21 | 'no_empty_statement' => true,
22 | 'no_multiline_whitespace_before_semicolons' => true,
23 | 'no_short_echo_tag' => true,
24 | 'no_trailing_whitespace' => true,
25 | 'no_trailing_whitespace_in_comment' => true,
26 | 'no_unneeded_control_parentheses' => ['return'],
27 | 'no_useless_return' => true,
28 | 'no_whitespace_before_comma_in_array' => true,
29 | 'no_whitespace_in_blank_line' => true,
30 | 'not_operator_with_successor_space' => false,
31 | 'semicolon_after_instruction' => true,
32 | 'standardize_not_equals' => true,
33 | 'ternary_operator_spaces' => true,
34 | 'ternary_to_null_coalescing' => true,
35 | 'trim_array_spaces' => true,
36 | 'whitespace_after_comma_in_array' => true,
37 | ];
38 | $excludes = [
39 | 'vendor',
40 | 'node_modules',
41 | ];
42 | return PhpCsFixer\Config::create()
43 | ->setRules($rules)
44 | ->setFinder(
45 | PhpCsFixer\Finder::create()
46 | ->exclude($excludes)
47 | ->notName('*.js')
48 | ->notName('*.css')
49 | ->notName('*.md')
50 | ->notName('*.xml')
51 | ->notName('*.yml')
52 | ->notName('*.tpl.php')
53 | );
54 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage: true
3 |
4 | checks:
5 | php:
6 | code_rating: true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 VectorFace, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SnappyRouter
2 |
3 | [](https://travis-ci.org/Vectorface/SnappyRouter)
4 | [](https://scrutinizer-ci.com/g/Vectorface/SnappyRouter/?branch=master)
5 | [](https://scrutinizer-ci.com/g/Vectorface/SnappyRouter/?branch=master)
6 | [](https://packagist.org/packages/Vectorface/Snappy-Router)
7 | [](https://packagist.org/packages/Vectorface/Snappy-Router)
8 |
9 | SnappyRouter is a lightweight router written in PHP. The router offers features
10 | standard in most other routers such as:
11 |
12 | - Controller/Action based routes
13 | - Rest-like routes with API versioning
14 | - Pattern matching routes (based off [nikic/FastRoute](https://github.com/nikic/FastRoute))
15 | - Direct file invocation (wrap paths to specific files through the router)
16 |
17 | SnappyRouter makes it easy to write your own routing handler for any imaginable
18 | custom routing scheme.
19 |
20 | *SnappyRouter is designed to work with your existing "seasoned"
21 | codebase to provide a common entry point for your code base.* SnappyRouter is
22 | ideal for existing projects that lack the features of a modern framework. By
23 | providing a number of flexible different routing handlers, any PHP code base
24 | can be retrofitted behind the router (usually) without requiring changes to
25 | your existing code. For more information on why you want to use a router,
26 | [see the documentation](https://snappyrouter.readthedocs.org/en/latest/#why-would-i-want-to-use-snappyrouter).
27 |
28 | For more information, view the detailed [documentation](https://snappyrouter.readthedocs.org/en/latest/).
29 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vectorface/snappy-router",
3 | "description": "A quick and snappy routing framework.",
4 | "keywords": [
5 | "MVC", "routing", "router", "drinking bird"
6 | ],
7 | "type": "library",
8 | "license": "MIT",
9 | "authors": [
10 | {
11 | "name": "Daniel Bruce",
12 | "email": "dbruce@vectorface.com",
13 | "role": "Developer"
14 | }
15 | ],
16 | "autoload": {
17 | "psr-4": {
18 | "Vectorface\\SnappyRouter\\": "./src/Vectorface/SnappyRouter"
19 | }
20 | },
21 | "autoload-dev": {
22 | "psr-4": {
23 | "Vectorface\\SnappyRouterTests\\": "./tests/Vectorface/SnappyRouterTests"
24 | }
25 | },
26 | "homepage": "https://github.com/Vectorface/snappy-router",
27 | "support": {
28 | "issues": "https://github.com/Vectorface/snappy-router/issues",
29 | "source": "https://github.com/Vectorface/snappy-router"
30 | },
31 | "require": {
32 | "php": ">=8.0",
33 | "vectorface/whip": "^0.5",
34 | "twig/twig": "^2.0",
35 | "nikic/fast-route":"^1.0.0",
36 | "psr/log": "^1.0 || ^2.0 || ^3.0",
37 | "ext-json": "*"
38 | },
39 | "require-dev": {
40 | "phpunit/phpunit": "^9.5.10",
41 | "squizlabs/php_codesniffer": "^2.0"
42 | },
43 | "scripts": {
44 | "test": [
45 | "@test-unit"
46 | ],
47 | "test-unit": "phpunit --color=always"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/di.md:
--------------------------------------------------------------------------------
1 | # Dependency Injection
2 |
3 | SnappyRouter provides a dependency injection (DI) layer for convenience and to
4 | improve code testability. At its core, the DI layer is simply a key/value pair
5 | matching strings to services. By providing your class dependencies using DI
6 | instead of direct instantiation, you can control the inner workings of a
7 | method at runtime through the use of mock and stub objects.
8 |
9 | DI also allows for a single point of instantiation for commonly used services
10 | such as CURL wrappers, mailers, database adapters, etc.
11 |
12 | ## Adding and Retrieving Services
13 |
14 | Services can be added to the DI layer either directly or as a callback. It is
15 | recommended to use a callback so that instantiation of the service can be
16 | delayed until needed.
17 |
18 | Example:
19 |
20 | ```php
21 | set('database', function(Vectorface\SnappyRouter\Di\Di $di) {
25 | return new \PDO(
26 | 'mysql:dbname=database;host=127.0.0.1',
27 | 'username',
28 | 'password'
29 | );
30 | });
31 | $db = $di->get('database');
32 | ...
33 | ```
34 |
35 | ## Specifying the DI Layer
36 |
37 | The SnappyRouter configuration allows for specifying a default DI class. This
38 | class can be your own code (subclassing the built-in class
39 | `Vectorface\SnappyRouter\Di\Di`). For example:
40 |
41 | ```php
42 | function(Di $di) {
55 | return new \PDO(
56 | 'mysql:dbname=database;host=127.0.0.1',
57 | 'username',
58 | 'password'
59 | );
60 | },
61 | ...
62 | ]);
63 | }
64 | }
65 | ```
66 |
67 | Next specify the DI class in the configuration.
68 |
69 | ```php
70 | 'Vendor\\MyNamespace\\Di\\MyDi',
76 | Config::KEY_HANDLERS => [
77 | ...
78 | ]
79 | ]);
80 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
81 | echo $router->handleRoute();
82 | ```
83 |
84 | ## Bootstrapping the DI Layer
85 |
86 | SnappyRouter also provides a default DI class that can be configured when your
87 | application bootstraps. For example:
88 |
89 | ```php
90 | set('database', function ($di) {
102 | // return the database
103 | })->set('mailer', function($di) {
104 | // return the mailer
105 | })->set('...', function($di) {
106 | ...
107 | });
108 |
109 | echo $router->handleRoute();
110 | ```
111 | ## Using the Built-in DI Layer
112 |
113 | Many of the classes provided by SnappyRouter also provide direct access to the
114 | DI layer for convenience. The interface
115 | `Vectorface\SnappyRouter\Di\DiProviderInterface` provides the two key methods
116 | `get` and `set`. This interface is implemented by popular classes such as
117 | `AbstractController`, `AbstractTask`, `AbstractPlugin`, etc.
118 |
119 | Here is an example with a custom controller that has access to the DI layer
120 | by simply extending `AbstractController`.
121 |
122 | ```php
123 | set('lastMethodCalled', __METHOD__);
135 |
136 | $database = $this->get('database'); // retrieve the database from the DI layer
137 | // do something with $database
138 | }
139 | }
140 | ```
141 |
--------------------------------------------------------------------------------
/docs/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | In this tutorial, we will build a new application from scratch using
4 | SnappyRouter.
5 |
6 | The full tutorial application can be found
7 | [here](https://github.com/Vectorface/SnappyTutorial).
8 |
9 | ## Creating the Project Structure
10 |
11 | We begin by creating the project folder and recommended subfolders.
12 |
13 | ```shell
14 | $> mkdir tutorial tutorial/app tutorial/public
15 | $> mkdir tutorial/app/Controllers tutorial/app/Views tutorial/app/Views/index tutorial/app/Models
16 | $> cd tutorial
17 | ```
18 |
19 | The folder structure should look like this:
20 |
21 | ```
22 | tutorial/
23 | app/
24 | Controllers/
25 | Models/
26 | Views/
27 | index/
28 | public/
29 | ```
30 |
31 | ## Redirects, Composer and index.php
32 |
33 | We will use .htaccess files to redirect all incoming requests to a single entry
34 | point in our application (`public/index.php`).
35 |
36 | Create the following files:
37 |
38 | ```
39 | #/tutorial/.htaccess
40 |
41 | RewriteEngine on
42 | RewriteRule ^$ public/ [L]
43 | RewriteRule (.*) public/$1 [L]
44 |
45 | ```
46 |
47 | ```
48 | #/tutorial/public/.htaccess
49 |
50 | RewriteEngine On
51 | RewriteCond %{REQUEST_FILENAME} !-d
52 | RewriteCond %{REQUEST_FILENAME} !-f
53 | RewriteRule ^(.*)$ index.php [QSA,L]
54 |
55 | ```
56 |
57 | We will also make use of Composer to provide dependencies and to autoload our
58 | application classes. If you do not have Composer installed, follow the
59 | documentation at [getcomposer.org](https://getcomposer.org/doc/00-intro.md).
60 |
61 | Create the file `tutorial/composer.json` with the following contents:
62 |
63 | ```json
64 | {
65 | "name": "vectorface/snappy-tutorial",
66 | "autoload": {
67 | "psr-4": {
68 | "Vectorface\\SnappyTutorial\\": "./app"
69 | }
70 | },
71 | "require": {
72 | "php": ">=7.0.0",
73 | "vectorface/snappy-router": "v0.3.0"
74 | }
75 | }
76 | ```
77 |
78 | and run
79 |
80 | ```shell
81 | $> composer install
82 | ```
83 |
84 | and finally the contents of `public/index.php`.
85 |
86 | ```php
87 | TutorialDi::class,
97 | Config::KEY_HANDLERS => [
98 | 'PageHandler' => [
99 | Config::KEY_CLASS => ControllerHandler::class,
100 | Config::KEY_OPTIONS => [
101 | Config::KEY_NAMESPACES => 'Vectorface\\SnappyTutorial\\Controllers',
102 | ControllerHandler::KEY_BASE_PATH => '/tutorial',
103 | ControllerHandler::KEY_VIEWS => [
104 | ControllerHandler::KEY_VIEWS_PATH => realpath(__DIR__.'/../app/Views')
105 | ]
106 | ]
107 | ]
108 | ]
109 | ]);
110 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
111 | echo $router->handleRoute();
112 | ```
113 |
114 | For simplicity, we include the configuration settings directly in
115 | `public/index.php`. It is probably a better practice to store these settings
116 | in a separate config file and include it in the index. For example:
117 |
118 | ```php
119 | ...
120 | $configArray = require_once __DIR__.'/../app/config.php';
121 | $config = new Config($configArray);
122 | ...
123 | ```
124 | *N.B.* Any file placed in the public folder will be directly accessible through
125 | the web browser. This folder should be used for any web assets (javascript, images,
126 | css, fonts) or direct PHP scripts you wish to expose. Any script exposed through
127 | the public folder will *not* be run through SnappyRouter.
128 |
129 | ## Setting up the DI Container
130 |
131 | Dependency injection (DI) is a powerful tool for injecting services and
132 | dependencies across your application at runtime. Some common examples include
133 | the database adapter, cache adapters, mail senders, etc.
134 |
135 | For this tutorial, we specify a class to use for DI. Create the file
136 | `app/Models/TutorialDi.php` with the following contents:
137 |
138 | ```php
139 | getDiArray());
151 | }
152 |
153 | protected function getDiArray()
154 | {
155 | return [
156 | 'projectTitle' => function(Di $di) {
157 | return 'SnappyRouter Tutorial';
158 | }
159 | ];
160 | }
161 | }
162 | ```
163 |
164 | This container registers only the `projectTitle` key.
165 |
166 | ## Controllers and Views
167 |
168 | We will set up an `IndexController` that extends our own abstract controller.
169 | It is good practice to always include your own base controller on top of
170 | `Vectorface\SnappyRouter\Controller\AbstractController` to provide common logic
171 | across all your controllers.
172 |
173 | The `BaseController` implements the `initialize` method which is invoked by
174 | SnappyRouter before any action is invoked. Note that we retrieve the
175 | `projectTitle` from the DI layer and hand it off to the view.
176 |
177 | ```php
178 | viewContext['projectTitle'] = $this->get('projectTitle');
193 | }
194 | }
195 | ```
196 | And the `IndexController`:
197 |
198 | ```php
199 | 'Hello SnappyRouter!'
209 | ];
210 | }
211 | }
212 | ```
213 |
214 | Note that there are many ways to pass variables to the view.
215 |
216 | 1. Using the associative array `$this->viewContext` provided by
217 | `Vectorface\SnappyRouter\Controller\AbstractController`.
218 | 2. Returning an associative array (this array will be merged with
219 | `$this->viewContext`).
220 | 3. Directly rendering the view with `$this->renderView`. More details for this
221 | method can be found [here](handlers/controller_handler/#integration-with-twig).
222 |
223 | We will divide our view into two files. The first file `app/Views/layout.twig` will
224 | provide common boilerplate that we could reuse across multiple pages.
225 |
226 | ```html
227 |
228 |
229 |
230 |
231 |
232 |
233 | {{ projectTitle|e }}
234 |
235 |
236 |
237 | {% block content %}
238 | {% endblock %}
239 |
240 |
241 |
242 |
243 | ```
244 |
245 | And a very simple view for our `indexAction` in `app/Views/index/index.twig`:
246 |
247 | ```html
248 | {% extends 'layout.twig' %}
249 |
250 | {% block content %}
251 |
252 |
{{ content }}
253 |
254 | {% endblock %}
255 | ```
256 |
257 | Once you add the `tutorial` folder to your standard web root, you should have
258 | a working application at `http://localhost/tutorial/`.
259 |
--------------------------------------------------------------------------------
/docs/handlers/cli_handler.md:
--------------------------------------------------------------------------------
1 | # CLI Task Handler
2 |
3 | The CLI (command line interface) task handler allows for execution of PHP in the
4 | standard shell instead of through a web server like Apache. Command line scripts
5 | are structured as tasks, which are similar to controllers (task/action pattern).
6 |
7 | Tasks must follow the naming convention `"${NAME}Task"`. Actions do not require
8 | any naming convention. Actions can (optionally) take an array as an argument
9 | which will be populated with any additional command line options passed to the
10 | script.
11 |
12 | An example task:
13 |
14 | ```php
15 | [
42 | 'CliHandler' => [
43 | Config::KEY_CLASS => CliTaskHandler::class,
44 | Config::KEY_OPTIONS => [
45 | Config::KEY_TASKS => [
46 | 'DatabaseTask' => 'Vendor\\MyNamespace\\Tasks\\DatabaseTask'
47 | ]
48 | ]
49 | ]
50 | ]
51 | ]);
52 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
53 | echo $router->handleRoute();
54 | ```
55 |
56 | Suppose the above code is in a file named `router.php`. To execute the cleanup
57 | action we can use the following command:
58 |
59 | ```shell
60 | $> php router.php --task Database --action cleanup
61 | ```
62 |
63 | # Specifying Tasks in the Configuration
64 |
65 | There are three ways to specify the list of tasks in the configuration. Tasks
66 | are listed in the `options` key within the handler.
67 |
68 | ## Explicit List of Tasks
69 |
70 | The list of tasks can be explicitly listed as a key/value pair. The key for the
71 | task must match the convention `"${NAME}Task"` and the value must be valid
72 | PHP class.
73 |
74 | Example:
75 |
76 | ```php
77 | ...
78 | Config::KEY_OPTIONS => [
79 | Config::KEY_TASKS => [
80 | 'DatabaseTask' => 'Vendor\\MyNamespace\\Tasks\\DatabaseTask',
81 | 'EmailTask' => 'Vendor\\MyNamespace\\Tasks\\SendEmailTask',
82 | ...
83 | ]
84 | ],
85 | ...
86 | ```
87 |
88 | ## Registering a list of Task Namespaces
89 |
90 | If your code is namespaced, you can register a list of namespaces for
91 | SnappyRouter to use to autodetect the appropriate task class.
92 |
93 | ```php
94 | ...
95 | Config::KEY_OPTIONS => [
96 | Config::KEY_NAMESPACES => [
97 | 'Vendor\\MyNamespace\\Tasks',
98 | 'Vendor\\AnotherNamespace\\Tasks',
99 | ...
100 | ]
101 | ],
102 | ...
103 | ```
104 |
105 | The namespaces will be scanned in the order listed in the array.
106 |
107 | ## Registering a Folder of Task PHP Files
108 |
109 | If your code is not namespaced, you can give SnappyRouter a list of folders
110 | to check (recursively) for a PHP file matching `${NAME}Task.php`.
111 |
112 | ```php
113 | ...
114 | Config::KEY_OPTIONS => [
115 | Config::KEY_FOLDERS => [
116 | '/home/user/project/app/tasks',
117 | '/home/user/project/app/moreTasks',
118 | ...
119 | ]
120 | ],
121 | ...
122 | ```
123 |
--------------------------------------------------------------------------------
/docs/handlers/direct_handler.md:
--------------------------------------------------------------------------------
1 | # Direct Script Handler
2 |
3 | The direct script handler maps web requests to raw PHP scripts. For PHP
4 | applications that do not use the front controller pattern and do not have a
5 | single entry point, this handler can be used to provide one without affecting
6 | any of the current code.
7 |
8 | ## How to use it
9 |
10 | The handler works by scanning the path for a specific prefix and matching it
11 | to a locally stored folder.
12 |
13 | For example we may have a folder structure like:
14 |
15 | ```
16 | /home/user/
17 | webroot/
18 | index.html
19 | scripts/
20 | test_script.php
21 | ```
22 |
23 | with our web server configured to use `/home/user/webroot` as the default
24 | document root. Accessing the script directly would be done through a url like:
25 |
26 | ```
27 | http://localhost/scripts/test_script.php
28 | ```
29 |
30 | The handler can then be configured as such:
31 |
32 | ```php
33 | [
40 | 'DirectHandler' => [
41 | Config::KEY_CLASS => DirectScriptHandler::class,
42 | Config::KEY_OPTIONS => [
43 | DirectScriptHandler::KEY_PATH_MAP => [
44 | '/scripts/' => '/home/user/webroot/scripts',
45 | '/' => '/home/user/webroot/scripts'
46 | ]
47 | ],
48 | Config::KEY_PLUGINS => [
49 | // optional list of plugins to put in front of your scripts
50 | ]
51 | ]
52 | ]
53 | ]);
54 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
55 | echo $router->handleRoute();
56 | ```
57 |
58 | ## Path Map Fallback Strategy
59 |
60 | Many application environments use virtual hosts, path aliases, symbolic
61 | links, etc. The URL to a particular script may not always be obvious and
62 | consistent across production, test, and local development environments.
63 | For this reason, the `DirectScriptHandler::KEY_PATH_MAP` config
64 | option supports multiple path prefixes which the handler will try each one
65 | iteratively (in order) until it finds a matching script.
66 |
67 | In the above example, we may have a virtual host pointing directly to the
68 | scripts folder, and the above configuration would continue to work. For example:
69 |
70 | ```
71 | http://livesite.example.com/test_script.php
72 | ```
73 |
--------------------------------------------------------------------------------
/docs/handlers/index.md:
--------------------------------------------------------------------------------
1 | # Handlers
2 |
3 | SnappyRouter uses a number of different route handlers to serve web and CLI
4 | requests. Handlers are a core component in the router and are the starting point
5 | for customizing the router for your own application.
6 |
7 | ## What is a Handler?
8 |
9 | The router makes very few assumptions about the executing environment (command
10 | line, web server, folder structure, etc). To handle each environment
11 | differently, the router passes off control to a handler. The handler is
12 | responsible for most of the routing logic.
13 |
14 | ## Built-in Handlers
15 |
16 | SnappyRouter provides a number of built in routing handlers.
17 |
18 | ### Controller Handler
19 |
20 | The controller handler provides the "VC" part of MVC. The router assumes your
21 | web requests match a pattern such as
22 | `/prefix/controller/action/param1/param2` and will attempt to find the
23 | appropriate controller and action to invoke. Furthermore, the Twig view engine
24 | can be initialized to provide an easy to use controller-view binding.
25 |
26 | [More details](controller_handler.md)
27 |
28 | ### Rest Handler
29 |
30 | The rest handler provides REST-like API urls such as
31 | `/api/v1.2/resource/1234` and extends the Controller Handler to route to an
32 | appropriate controller. Responses are encoded as JSON by default.
33 |
34 | [More details](rest_handler.md)
35 |
36 | ### Pattern Match Handler
37 |
38 | The pattern match handler uses the powerful
39 | [FastRoute](https://github.com/nikic/FastRoute) library to allow custom routes
40 | to map directly to a callable function. This handler will seem familiar to users
41 | of [Silex](http://silex.sensiolabs.org/) or [Express.js](http://expressjs.com/).
42 |
43 | [More details](pattern_handler.md)
44 |
45 | ### Direct Script Handler
46 |
47 | This handler allows the route to fall through to an existing PHP script. If your
48 | application entry points are paths to scripts directly this handler can be used
49 | to wrap access to those scripts through the router.
50 |
51 | [More details](direct_handler.md)
52 |
53 | ### CLI Task Handler
54 |
55 | This handler provides a command line entry point for tasks.
56 |
57 | [More details](cli_handler.md)
58 |
59 | ### JSON-RPC Handler
60 |
61 | This handler is for exposing class methods to remote clients via the JSON-RPC
62 | protocol, versions 1 and 2.
63 |
64 | [More details](jsonrpc_handler.md)
65 |
66 |
67 | ## Writing your own Handler
68 |
69 | Every application has unique conventions and workflows. SnappyRouter handlers
70 | are easy to extend and building you can write your own handler for any custom
71 | routing your application may need.
72 |
73 | To begin, add a class that extends one of the abstract handler classes. For a
74 | web request handler, it is recommended to extend
75 | `Vectorface\\SnappyRouter\\Handler\\AbstractRequestHandler`.
76 |
77 | ```php
78 | [
122 | 'MyHandler' => [
123 | Config::KEY_CLASS => MyCustomHandler::class,
124 | Config::KEY_OPTIONS => [
125 | // an array of options
126 | ]
127 | ]
128 | ]
129 | ]);
130 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
131 | echo $router->handleRoute();
132 | ```
133 |
--------------------------------------------------------------------------------
/docs/handlers/jsonrpc_handler.md:
--------------------------------------------------------------------------------
1 | # JSON-RPC 1.0 + 2.0 Handler
2 |
3 | The class `Vectorface\SnappyRouter\Handler\JsonRpcHandler` provides a means of
4 | calling class methods via the
5 | [JSON-RPC](http://json-rpc.org/wiki/specification) protocol. Version 1.0 and
6 | 2.0 of the protocol should both be fully supported.
7 |
8 | Some items of interest in this implementation:
9 |
10 | * Supports batch calls; Many calls in a single request.
11 | * Notification calls; Drops responses without a request id.
12 | * Handles both parameter arrays, and named parameters.
13 | * Transparently handles both the JSON-RPC 1.0 and 2.0 spec.
14 | * JSON-RPC 1.0 class hinting *is deliberately not supported*.
15 |
16 | ## Why JSON-RPC?
17 |
18 | JSON-RPC allows calling server-side methods on the client side nearly
19 | transparently. The only limitations are generally limitations of JSON
20 | serialization; PHP associative arrays become untyped JSON objects.
21 |
22 | Put differently, a remote procedure call can be abstracted out to look almost
23 | identical to a local method call, making it very easy to integrate server-side
24 | calls into client-side or remote code.
25 |
26 | API clients can also be simpler too because the local and remote method
27 | signatures can be the same. There is no need to map URLs and/or parameters as
28 | in REST APIs.
29 |
30 | For example, one could expose the following class on the server:
31 |
32 | ```php
33 | add(1, 1); // 2!
51 | ```
52 |
53 | Or it could be called remotely:
54 |
55 | ```php
56 | $adder = new MyJsonRpcClient("http://.../Adder"); // Any JSON-RPC client.
57 | $adder->add(1, 1); // 2!
58 | ```
59 |
60 | ## Usage
61 |
62 | To expose the example `Adder` class listed in the previous section, configure a
63 | router instance as follows:
64 |
65 | ```php
66 | [
74 | 'JsonRpcHandler' => [
75 | Config::KEY_CLASS => JsonRpcHandler::class,
76 | Config::KEY_OPTIONS => [
77 | Config::KEY_SERVICES => [
78 | 'Adder' => Adder::class, // Adder, as above
79 | ],
80 | ]
81 | ]
82 | ]
83 | ]);
84 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
85 | echo $router->handleRoute();
86 | ```
87 |
88 | If the router is called with a URI ending in `Adder(.php)` and a valid JSON-RPC
89 | request POSTed for the method "add", the router will response with a JSON-RPC
90 | encoded response with the sum of the two arguments.
91 |
--------------------------------------------------------------------------------
/docs/handlers/pattern_handler.md:
--------------------------------------------------------------------------------
1 | # Pattern Match Handler
2 |
3 | Direct pattern matching in SnappyRouter is supported by the pattern match
4 | handler. Similar to many other popular routers, the routing configuration is
5 | specified as a list of regular expression patterns mapping to callback
6 | functions.
7 |
8 | An example configuration:
9 |
10 | ```php
11 | [
18 | 'PatternHandler' => [
19 | Config::KEY_CLASS => PatternMatchHandler::class,
20 | Config::KEY_OPTIONS => [
21 | PatternMatchHandler::KEY_ROUTES => [
22 | '/users/{name}/{id:[0-9]+}' => [
23 | 'get' => function ($routeParams) {
24 | // invoked only for GET calls
25 | },
26 | 'post' => function ($routeParams) {
27 | // invoked only for POST calls
28 | }
29 | ],
30 | '/users' => function($routeParams) {
31 | // invoked for all HTTP verbs
32 | }
33 | ]
34 | ]
35 | ]
36 | ]
37 | ]);
38 | $router = new Vectorface\SnappyRouter\SnappyRouter($config);
39 | echo $router->handleRoute();
40 | ```
41 |
42 | ## Specifying Routes
43 |
44 | Routes are listed as arrays using regular expressions with named parameters. For the documentation on the individual patterns see the
45 | [FastRoute library](https://github.com/nikic/FastRoute). The routes must be
46 | specified in the options of the handler.
47 |
48 | The patterns should be listed as the keys to the array and must map to a
49 | `callable` function or another array with HTTP verbs as keys.
50 |
51 | ### Examples
52 |
53 | A route using the same callback for all HTTP verbs.
54 |
55 | ```php
56 | ...
57 | PatternMatchHandler::KEY_ROUTES => [
58 | '/api/{version}/{controller}/{action}' => function ($routeParams) {
59 | // invoked for all HTTP verbs
60 | }
61 | ],
62 | ...
63 | ```
64 |
65 | A route specifying individual HTTP verbs.
66 |
67 | ```php
68 | ...
69 | PatternMatchHandler::KEY_ROUTES => [
70 | '/api/{version}/{controller}/{action}' => [
71 | 'get' => function ($routeParams) {
72 | // handle GET requests
73 | },
74 | 'post' => function ($routeParams) {
75 | // handle POST requests
76 | },
77 | 'put' => function ($routeParams) {
78 | // handle PUT requests
79 | },
80 | 'delete' => function ($routeParams) {
81 | // handle DELETE requests
82 | },
83 | 'options' => function ($routeParams) {
84 | // handle OPTIONS requests
85 | },
86 | 'head' => function ($routeParams) {
87 | // handle HEAD requests
88 | }
89 | ]
90 | ],
91 | ...
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/handlers/rest_handler.md:
--------------------------------------------------------------------------------
1 | # Rest Handler
2 |
3 | The class `Vectorface\SnappyRouter\Handler\RestHandler` provides a simple
4 | "by convention" api routing handler that builds on top of the
5 | [controller handler](handlers/controller_handler) by mapping specific route
6 | patterns to controllers and actions.
7 |
8 | ## Rest Routing
9 |
10 | The following route patterns are supported:
11 |
12 | ```
13 | /(optional/base/path/)v{$apiVersion}/${controller}/${objectId}/${action}
14 | /(optional/base/path/)v{$apiVersion}/${controller}/${action}/${objectId}
15 | /(optional/base/path/)v{$apiVersion}/${controller}/${action}
16 | /(optional/base/path/)v{$apiVersion}/${controller}/${objectId}
17 | /(optional/base/path/)v{$apiVersion}/${controller}
18 | ```
19 |
20 | Examples:
21 |
22 | ```
23 | /api/v1.2/users/1234/details
24 | /api/v1.2/users/details/1234
25 | /api/v1.2/users/search
26 | /api/v1.2/users/1234
27 | /api/v1.2/users
28 | ```
29 |
30 | ## JSON Serialization
31 |
32 | Unlike the Twig view handler used in standard controller handler, the rest
33 | handler is configured by default to encode all responses in JSON text. To
34 | use a different encoder it is recommended to subclass the `RestHandler` class
35 | and override a couple of methods.
36 |
37 | Example:
38 |
39 | ```php
40 | getDetails();
92 | }
93 | }
94 | ```
95 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # SnappyRouter
2 |
3 | SnappyRouter is a lightweight router written in PHP. The router offers features
4 | standard in most other routers such as:
5 |
6 | - Controller/Action based routes
7 | - Rest-like routes with API versioning
8 | - Pattern matching routes (based off [nikic/FastRoute](https://github.com/nikic/FastRoute))
9 | - Direct file invocation (wrap paths to specific files through the router)
10 |
11 | SnappyRouter makes it easy to write your own routing handler for any imaginable
12 | custom routing scheme. It is designed to work with your existing "seasoned"
13 | codebase to provide a common entry point for your code base.
14 |
15 | ## What makes SnappyRouter unique?
16 |
17 | SnappyRouter is very fast and flexible. The router can be put in front of
18 | existing PHP scripts with very little noticeable overhead. The core design of
19 | the router means it gets out of the way quickly and executes your own code as
20 | soon as possible. *You should be able to add SnappyRouter to your existing
21 | project without modifying any existing code*.
22 |
23 | SnappyRouter supports PHP 5.3, 5.4, 5.5, and 5.6, as well as HHVM.
24 |
25 | ## Why would I want to use SnappyRouter?
26 |
27 | Modern best practices in PHP applications has lead to the so-called
28 | [front controller pattern](https://en.wikipedia.org/wiki/Front_Controller_pattern)
29 | (a single entry point to your application). The benefits of a single entry
30 | point include:
31 |
32 | - Better flexibility over global initialization and shut down (say goodbye to
33 | "global.php", "common.php" and auto_prepend_file directives).
34 | - Easier to manage code base due to each entry point not needing to include
35 | global setup code.
36 | - A more consistent project code base (your project is no longer a collection
37 | of related PHP scripts).
38 | - Flexible pretty URLs (great for SEO and application UX).
39 |
--------------------------------------------------------------------------------
/docs/plugins/index.md:
--------------------------------------------------------------------------------
1 | # Plugins
2 |
3 | SnappyRouter provides a basic plugin system allowing you to interject your own
4 | code before the router hands off control and after the route has finished.
5 |
6 | Note that to interrupt the standard route, the plugin method *must* throw an
7 | exception. The return value of the plugin methods are not used.
8 |
9 | ## Enabling Plugins
10 |
11 | Plugins are specified in the configuration under options for each handler.
12 | Plugins can be specified as an arbitrary key mapping to the name of the
13 | class or as an array with fields for the file and class.
14 |
15 | ```php
16 | ...
17 | Config::KEY_HANDLERS => array(
18 | 'MyHandler' => array(
19 | Config::KEY_CLASS => 'Vendor\\MyNamespace\\Handler\\MyCustomHandler',
20 | Config::KEY_OPTIONS => array(
21 | Config::KEY_PLUGINS => array(
22 | 'RouterHeaderPlugin' => 'Vectorface\\SnappyRouter\\Plugin\\HttpHeader\\RouterHeaderPlugin',
23 | 'MyCustomPlugin' => array(
24 | Config::KEY_CLASS => '\MyCustomPlugin',
25 | Config::KEY_FILE => '/home/user/project/plugins/MyCustomPlugin.php'
26 | )
27 | )
28 | )
29 | )
30 | )
31 | ...
32 | ```
33 |
34 | ## Writing your own Plugin
35 |
36 | Plugins are very easy to implement, simply extend the class
37 | `Vectorface\SnappyRouter\Plugin\AbstractPlugin` and implement the desired
38 | methods.
39 |
40 | Note that you *do not* need to implement all the methods. You are free to
41 | implement only the methods that you care about.
42 |
43 | Example:
44 |
45 | ```php
46 | get('authentication');
93 | if ($authentication->isAuthorized() === false) {
94 | throw new UnauthorizedException('Authorization not valid.');
95 | }
96 | }
97 | }
98 | ...
99 | ```
100 |
101 | ### `afterFullRouteInvoked`
102 |
103 | The method `afterFullRouteInvoked` is (unsurprisingly) invoked after the
104 | router has invoked the route. This method is primarily to allow for clean up,
105 | logging, etc.
106 |
107 | An example demonstrating a specific call being logged.
108 |
109 | ```php
110 | getRequest();
116 | if ($request->getController() === 'SomeController' &&
117 | $request->getAction() === 'SomeAction') {
118 | $this->get('logger')->log('SomeAction was invoked.');
119 | }
120 | }
121 | }
122 | ...
123 | ```
124 |
125 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: 'SnappyRouter'
2 | pages:
3 | - Home:
4 | - Home: index.md
5 | - Getting Started: getting_started.md
6 | - Handlers:
7 | - Handlers: handlers/index.md
8 | - Controller Handler: handlers/controller_handler.md
9 | - Rest Handler: handlers/rest_handler.md
10 | - Pattern Match Handler: handlers/pattern_handler.md
11 | - Direct Script Handler: handlers/direct_handler.md
12 | - JSON RPC Handler: handlers/jsonrpc_handler.md
13 | - CLI Task Handler: handlers/cli_handler.md
14 | - Plugins:
15 | - Plugins: plugins/index.md
16 | - Dependency Injection:
17 | - Dependency Injection: di.md
18 | theme: 'readthedocs'
19 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 | tests/
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Authentication/AbstractAuthenticator.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | abstract class AbstractAuthenticator implements AuthenticatorInterface
12 | {
13 | }
14 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Authentication/AuthenticatorInterface.php:
--------------------------------------------------------------------------------
1 | realAuthMechanism = $wrappedAuthMechanism;
13 | * }
14 | * public function authenticate($cred) {
15 | * return $this->realAuthMechanism->login($cred->user, $cred->pass);
16 | * }
17 | * }
18 | * \endcode
19 | *
20 | * @copyright Copyright (c) 2014, VectorFace, Inc.
21 | * @author J. Anderson
22 | */
23 | interface AuthenticatorInterface
24 | {
25 | /**
26 | * Authenticate a set of credentials, typically a username and password.
27 | *
28 | * @param mixed $credentials Credentials in some form; A string username and password, an auth token, etc.
29 | * @return bool Returns true if the identity was authenticated, or false otherwise.
30 | */
31 | public function authenticate($credentials);
32 | }
33 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Authentication/CallbackAuthenticator.php:
--------------------------------------------------------------------------------
1 | login($credentials['username'], $credentials['password']);
14 | * });
15 | * \endcode
16 | *
17 | * @copyright Copyright (c) 2014, VectorFace, Inc.
18 | * @author J. Anderson
19 | */
20 | class CallbackAuthenticator extends AbstractAuthenticator
21 | {
22 | /** Stores the callback to be used for authentication.*/
23 | private $callback;
24 |
25 | /**
26 | * Wrap another authentication mechanism via a callback.
27 | *
28 | * @param Closure $callback The callback, which is expected to have the same signature as $this->authenticate.
29 | */
30 | public function __construct(Closure $callback)
31 | {
32 | $this->callback = $callback;
33 | }
34 |
35 | /**
36 | * Authenticate a set of credentials using a callback.
37 | *
38 | * @param mixed $credentials One or more credentials; A string password, or an array for multi-factor auth.
39 | * @return bool Returns true if the identity was authenticated, or false otherwise.
40 | */
41 | public function authenticate($credentials)
42 | {
43 | return (bool)call_user_func($this->callback, $credentials);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Config/Config.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Config implements ArrayAccess, ConfigInterface
14 | {
15 | /** the config key for the list of handlers */
16 | const KEY_HANDLERS = 'handlers';
17 | /** the config key for the DI provider */
18 | const KEY_DI = 'di';
19 | /** the config key for the list of handler options */
20 | const KEY_OPTIONS = 'options';
21 | /** the config key for a class */
22 | const KEY_CLASS = 'class';
23 | /** the config key for a file */
24 | const KEY_FILE = 'file';
25 | /** the config key for the list of services (deprecated) */
26 | const KEY_SERVICES = 'services';
27 | /** the config key for the list of controllers */
28 | const KEY_CONTROLLERS = 'services';
29 | /** the config key for the list of plugins */
30 | const KEY_PLUGINS = 'plugins';
31 | /** the config key for the list of controller namespaces */
32 | const KEY_NAMESPACES = 'namespaces';
33 | /** the config key for the list of controller folders */
34 | const KEY_FOLDERS = 'folders';
35 | /** the config key for the list of tasks */
36 | const KEY_TASKS = 'tasks';
37 | /** the config key for debug mode */
38 | const KEY_DEBUG = 'debug';
39 |
40 | // the internal config array
41 | private $config;
42 |
43 | /**
44 | * Constructor for the class.
45 | * @param mixed $config An array of config settings (or something that easily
46 | * typecasts to an array like an stdClass).
47 | */
48 | public function __construct($config)
49 | {
50 | $this->config = (array)$config;
51 | }
52 |
53 | /**
54 | * Returns whether or not the given key exists in the config.
55 | * @param string $offset The key to be checked.
56 | * @return bool Returns true if the key exists and false otherwise.
57 | */
58 | #[\ReturnTypeWillChange]
59 | public function offsetExists($offset)
60 | {
61 | return isset($this->config[$offset]);
62 | }
63 |
64 | /**
65 | * Returns the value associated with the key or null if no value exists.
66 | * @param string $offset The key to be fetched.
67 | * @return bool Returns the value associated with the key or null if no value exists.
68 | */
69 | #[\ReturnTypeWillChange]
70 | public function offsetGet($offset)
71 | {
72 | return $this->offsetExists($offset) ? $this->config[$offset] : null;
73 | }
74 |
75 | /**
76 | * Sets the value associated with the given key.
77 | *
78 | * @param string $offset The key to be used.
79 | * @param mixed $value The value to be set.
80 | * @throws Exception
81 | */
82 | #[\ReturnTypeWillChange]
83 | public function offsetSet($offset, $value)
84 | {
85 | if (null === $offset) {
86 | throw new Exception('Config values must contain a key.');
87 | }
88 | $this->config[$offset] = $value;
89 | }
90 |
91 | /**
92 | * Removes the value set to the given key.
93 | * @param string $offset The key to unset.
94 | * @return void
95 | */
96 | #[\ReturnTypeWillChange]
97 | public function offsetUnset($offset)
98 | {
99 | unset($this->config[$offset]);
100 | }
101 |
102 | /**
103 | * Returns the value associated with the given key. An optional default value
104 | * can be provided and will be returned if no value is associated with the key.
105 | * @param string $key The key to be used.
106 | * @param mixed $defaultValue The default value to return if the key currently
107 | * has no value associated with it.
108 | * @return mixed Returns the value associated with the key or the default value if
109 | * no value is associated with the key.
110 | */
111 | public function get($key, $defaultValue = null)
112 | {
113 | return $this->offsetExists($key) ? $this->offsetGet($key) : $defaultValue;
114 | }
115 |
116 | /**
117 | * Sets the current value associated with the given key.
118 | *
119 | * @param string $key The key to be set.
120 | * @param mixed $value The value to be set to the key.
121 | * @throws Exception
122 | */
123 | public function set($key, $value)
124 | {
125 | $this->offsetSet($key, $value);
126 | }
127 |
128 | /**
129 | * Returns an array representation of the whole configuration.
130 | * @return array An array representation of the whole configuration.
131 | */
132 | public function toArray()
133 | {
134 | return $this->config;
135 | }
136 |
137 | /**
138 | * Returns whether or not we are in debug mode.
139 | * @return boolean Returns true if the router is in debug mode and false
140 | * otherwise.
141 | */
142 | public function isDebug()
143 | {
144 | return (bool)$this->get(self::KEY_DEBUG, false);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Config/ConfigInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface ConfigInterface
12 | {
13 | /**
14 | * Returns the value associated with the given key. An optional default value
15 | * can be provided and will be returned if no value is associated with the key.
16 | * @param string $key The key to be used.
17 | * @param mixed $defaultValue The default value to return if the key currently
18 | * has no value associated with it.
19 | * @return mixed Returns the value associated with the key or the default value if
20 | * no value is associated with the key.
21 | */
22 | public function get($key, $defaultValue = null);
23 |
24 | /**
25 | * Sets the current value associated with the given key.
26 | * @param string $key The key to be set.
27 | * @param mixed $value The value to be set to the key.
28 | * @return void
29 | */
30 | public function set($key, $value);
31 |
32 | /**
33 | * Returns an array representation of the whole configuration.
34 | * @return array An array representation of the whole configuration.
35 | */
36 | public function toArray();
37 | }
38 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Controller/AbstractController.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | abstract class AbstractController implements DiProviderInterface
17 | {
18 | /** The web request being made. */
19 | private $request;
20 |
21 | /** The array of view context variables. */
22 | protected $viewContext;
23 |
24 | /** The handler being used by the router. */
25 | protected $handler;
26 |
27 | /**
28 | * This method is called before invoking any specific controller action.
29 | * Override this method to provide your own logic for the subclass but
30 | * ensure you make a call to parent::initialize() as well.
31 | * @param HttpRequest $request The web request being made.
32 | * @param AbstractRequestHandler $handler The handler the router is using.
33 | * @return AbstractController Returns $this.
34 | */
35 | public function initialize(HttpRequest $request, AbstractRequestHandler $handler)
36 | {
37 | $this->request = $request;
38 | $this->handler = $handler;
39 | $this->viewContext = [];
40 | return $this;
41 | }
42 |
43 | /**
44 | * Renders the view for the given controller and action.
45 | *
46 | * @param array $viewVariables An array of additional parameters to add
47 | * to the existing view context.
48 | * @param string $template The name of the view template.
49 | * @return string The rendered view as a string.
50 | * @throws Exception
51 | */
52 | public function renderView($viewVariables, $template)
53 | {
54 | $encoder = $this->handler->getEncoder();
55 | if (method_exists($encoder, 'renderView')) {
56 | return $encoder->renderView(
57 | $template,
58 | array_merge($this->viewContext, (array)$viewVariables)
59 | );
60 | } else {
61 | throw new Exception('The current encoder does not support the render view method.');
62 | }
63 | }
64 |
65 | /**
66 | * Returns the request object.
67 | * @return HttpRequest The request object.
68 | */
69 | public function getRequest()
70 | {
71 | return $this->request;
72 | }
73 |
74 | /**
75 | * Returns the view context.
76 | * @return array The view context.
77 | */
78 | public function getViewContext()
79 | {
80 | return $this->viewContext;
81 | }
82 |
83 | /**
84 | * Retrieve an element from the DI container.
85 | *
86 | * @param string $key The DI key.
87 | * @param boolean $useCache (optional) An optional indicating whether we
88 | * should use the cached version of the element (true by default).
89 | * @return mixed Returns the DI element mapped to that key.
90 | * @throws Exception
91 | */
92 | public function get($key, $useCache = true)
93 | {
94 | return Di::getDefault()->get($key, $useCache);
95 | }
96 |
97 | /**
98 | * Sets an element in the DI container for the specified key.
99 | * @param string $key The DI key.
100 | * @param mixed $element The DI element to store.
101 | * @return Di Returns the Di instance.
102 | */
103 | public function set($key, $element)
104 | {
105 | return Di::getDefault()->set($key, $element);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Di/Di.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Di implements DiInterface
13 | {
14 | private $elements; // a cache of instantiated elements
15 | private $elementMap; // the map between keys and their elements
16 |
17 | private static $instance; // a static instance of this class for static use
18 |
19 | /**
20 | * Constructor for the class.
21 | * @param array $elementMap An optional initial set of elements to use.
22 | */
23 | public function __construct($elementMap = [])
24 | {
25 | $this->elementMap = is_array($elementMap) ? $elementMap : [];
26 | $this->elements = [];
27 | }
28 |
29 | /**
30 | * Returns the element associated with the specified key.
31 | * @param string $element The key for the element.
32 | * @param boolean $useCache An optional flag for whether we can use the
33 | * cached version of the element (defaults to true).
34 | * @return mixed Returns the associated element.
35 | * @throws Exception Throws an exception if no element is registered for
36 | * the given key.
37 | */
38 | public function get($element, $useCache = true)
39 | {
40 | if ($useCache && isset($this->elements[$element])) {
41 | // return the cached version
42 | return $this->elements[$element];
43 | }
44 |
45 | if (isset($this->elementMap[$element])) {
46 | if (is_callable($this->elementMap[$element])) {
47 | // if we have callback, invoke it and cache the result
48 | $this->elements[$element] = call_user_func(
49 | $this->elementMap[$element],
50 | $this
51 | );
52 | } else {
53 | // otherwise simply cache the result and return it
54 | $this->elements[$element] = $this->elementMap[$element];
55 | }
56 | return $this->elements[$element];
57 | }
58 |
59 | throw new Exception('No element registered for key: '.$element);
60 | }
61 |
62 | /**
63 | * Assigns a specific element to the given key. This method will override
64 | * any previously assigned element for the given key.
65 | * @param string $element The key for the specified element.
66 | * @param mixed $value The specified element. This can be an instance of the
67 | * element or a callback to be invoked.
68 | * @return self $this.
69 | */
70 | public function set($element, $value)
71 | {
72 | // clear the cached element
73 | unset($this->elements[$element]);
74 | $this->elementMap[$element] = $value;
75 | return $this;
76 | }
77 |
78 | /**
79 | * Returns whether or not a given element has been registered.
80 | * @param string $element The key for the element.
81 | * @return bool true if the element is registered and false otherwise.
82 | */
83 | public function hasElement($element)
84 | {
85 | return isset($this->elementMap[$element]);
86 | }
87 |
88 | /**
89 | * Returns an array of all registered keys.
90 | * @return array An array of all registered keys.
91 | */
92 | public function allRegisteredElements()
93 | {
94 | return array_keys($this->elementMap);
95 | }
96 |
97 | /**
98 | * Returns the current default DI instance.
99 | * @return Di The current default DI instance.
100 | */
101 | public static function getDefault()
102 | {
103 | if (isset(self::$instance)) {
104 | return self::$instance;
105 | }
106 | self::$instance = new self();
107 | return self::$instance;
108 | }
109 |
110 | /**
111 | * Sets the current default DI instance..
112 | * @param DiInterface $instance An instance of DI.
113 | * @return DiInterface Returns the new default DI instance.
114 | */
115 | public static function setDefault(DiInterface $instance)
116 | {
117 | self::$instance = $instance;
118 | return self::$instance;
119 | }
120 |
121 | /**
122 | * Clears the current default DI instance.
123 | */
124 | public static function clearDefault()
125 | {
126 | self::$instance = null;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Di/DiInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | interface DiInterface
15 | {
16 | /**
17 | * Returns the element associated with the specified key.
18 | * @param string $element The key for the element.
19 | * @param boolean $useCache An optional flag for whether we can use the
20 | * cached version of the element (defaults to true).
21 | * @return mixed Returns the associated element.
22 | * @throws Exception Throws an exception if no element is registered for
23 | * the given key.
24 | */
25 | public function get($element, $useCache = true);
26 |
27 | /**
28 | * Assigns a specific element to the given key. This method will override
29 | * any previously assigned element for the given key.
30 | * @param string $element The key for the specified element.
31 | * @param mixed $value The specified element. This can be an instance of the
32 | * element or a callback to be invoked.
33 | * @return DiInterface Returns $this.
34 | */
35 | public function set($element, $value);
36 |
37 | /**
38 | * Returns whether or not a given element has been registered.
39 | * @param string $element The key for the element.
40 | * @return boolean Returns true if the element is registered and false otherwise.
41 | */
42 | public function hasElement($element);
43 |
44 | /**
45 | * Returns an array of all registered elements (their keys).
46 | * @return array An array of all registered elements (their keys).
47 | */
48 | public function allRegisteredElements();
49 | }
50 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Di/DiProviderInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface DiProviderInterface
12 | {
13 | /**
14 | * Retrieve an element from the DI container.
15 | * @param string $key The DI key.
16 | * @param boolean $useCache (optional) An optional indicating whether we
17 | * should use the cached version of the element (true by default).
18 | * @return mixed Returns the DI element mapped to that key.
19 | */
20 | public function get($key, $useCache = true);
21 |
22 | /**
23 | * Sets an element in the DI container for the specified key.
24 | * @param string $key The DI key.
25 | * @param mixed $element The DI element to store.
26 | * @return Di Returns the Di instance.
27 | */
28 | public function set($key, $element);
29 | }
30 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/AbstractEncoder.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | abstract class AbstractEncoder implements EncoderInterface
12 | {
13 | // an array of options for the encoder
14 | private $options;
15 |
16 | /**
17 | * Constructor for the encoder.
18 | * @param array $options An array of encoder options.
19 | */
20 | public function __construct($options = [])
21 | {
22 | $this->options = (array)$options;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/EncoderInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface EncoderInterface
14 | {
15 | /**
16 | * @param AbstractResponse $response The response to be encoded.
17 | * @return string Returns the response encoded as a string.
18 | */
19 | public function encode(AbstractResponse $response);
20 | }
21 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/JsonEncoder.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class JsonEncoder extends AbstractEncoder
14 | {
15 | /**
16 | * @param AbstractResponse $response The response to be encoded.
17 | * @return string Returns the response encoded as a string.
18 | * @throws EncoderException
19 | */
20 | public function encode(AbstractResponse $response)
21 | {
22 | $responseObject = $response->getResponseObject();
23 | if (null === $responseObject || is_array($responseObject) || is_scalar($responseObject)) {
24 | return json_encode($responseObject);
25 | } elseif (is_object($responseObject)) {
26 | if (method_exists($responseObject, 'jsonSerialize')) {
27 | return json_encode($responseObject->jsonSerialize());
28 | }
29 |
30 | return json_encode(get_object_vars($responseObject));
31 | }
32 |
33 | throw new EncoderException('Unable to encode response as JSON.');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/JsonpEncoder.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class JsonpEncoder extends JsonEncoder
15 | {
16 | /** the config key for the client side method to invoke */
17 | const KEY_CLIENT_METHOD = 'clientMethod';
18 |
19 | /** The method the client is invoking. */
20 | private $clientMethod;
21 |
22 | /**
23 | * Constructor for the encoder.
24 | *
25 | * @param array $options (optional) The array of plugin options.
26 | * @throws Exception
27 | */
28 | public function __construct($options = [])
29 | {
30 | parent::__construct($options);
31 | if (!isset($options[self::KEY_CLIENT_METHOD])) {
32 | throw new Exception('Client method missing from plugin options.');
33 | }
34 | $this->clientMethod = (string)$options[self::KEY_CLIENT_METHOD];
35 | }
36 |
37 | /**
38 | * @param AbstractResponse $response The response to be encoded.
39 | * @return string Returns the response encoded in JSON.
40 | * @throws EncoderException
41 | */
42 | public function encode(AbstractResponse $response)
43 | {
44 | return sprintf(
45 | '%s(%s);',
46 | $this->clientMethod,
47 | parent::encode($response)
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/NullEncoder.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class NullEncoder extends AbstractEncoder
13 | {
14 | /**
15 | * @param AbstractResponse $response The response to be encoded.
16 | * @return string Returns the response encoded as a string.
17 | */
18 | public function encode(AbstractResponse $response)
19 | {
20 | if (is_string($response->getResponseObject())) {
21 | return $response->getResponseObject();
22 | }
23 |
24 | return '';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Encoder/TwigViewEncoder.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class TwigViewEncoder extends AbstractEncoder
20 | {
21 | // the template to encode
22 | private $template;
23 |
24 | // the twig view environment
25 | private $viewEnvironment;
26 |
27 | /**
28 | * Constructor for the encoder.
29 | *
30 | * @param array $viewConfig The view configuration.
31 | * @param string $template The name of the default template to render
32 | * @noinspection PhpMissingParentConstructorInspection
33 | * @throws InternalErrorException
34 | */
35 | public function __construct($viewConfig, $template)
36 | {
37 | if (!isset($viewConfig[ControllerHandler::KEY_VIEWS_PATH])) {
38 | throw new InternalErrorException(
39 | 'View environment missing views path.'
40 | );
41 | }
42 | $loader = new FilesystemLoader($viewConfig[ControllerHandler::KEY_VIEWS_PATH]);
43 | $this->viewEnvironment = new Environment($loader, $viewConfig);
44 | $this->template = $template;
45 | }
46 |
47 | /**
48 | * Returns the Twig view environment.
49 | * @return Environment The configured twig environment.
50 | */
51 | public function getViewEnvironment()
52 | {
53 | return $this->viewEnvironment;
54 | }
55 |
56 | /**
57 | * @param AbstractResponse $response The response to be encoded.
58 | * @return string Returns the response encoded as a string.
59 | * @throws LoaderError|RuntimeError|SyntaxError
60 | */
61 | public function encode(AbstractResponse $response)
62 | {
63 | $responseObject = $response->getResponseObject();
64 | if (is_string($responseObject)) {
65 | return $responseObject;
66 | }
67 |
68 | return $this->viewEnvironment
69 | ->load($this->template)
70 | ->render((array)$responseObject);
71 | }
72 |
73 | /**
74 | * Renders an arbitrary view with arbitrary parameters.
75 | *
76 | * @param string $template The template to render.
77 | * @param array $variables The variables to use.
78 | * @return string Returns the rendered template as a string.
79 | * @throws LoaderError|RuntimeError|SyntaxError
80 | */
81 | public function renderView($template, $variables)
82 | {
83 | return $this->getViewEnvironment()
84 | ->load($template)
85 | ->render((array)$variables);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/AccessDeniedException.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class AccessDeniedException extends Exception implements RouterExceptionInterface
14 | {
15 | /**
16 | * Returns the associated status code with the exception.
17 | * @return int The associated status code.
18 | */
19 | public function getAssociatedStatusCode()
20 | {
21 | return AbstractResponse::RESPONSE_FORBIDDEN;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/EncoderException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class EncoderException extends InternalErrorException
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/HandlerException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class HandlerException extends InternalErrorException
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/InternalErrorException.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class InternalErrorException extends Exception implements RouterExceptionInterface
14 | {
15 | /**
16 | * Returns the associated status code with the exception. By default, most exceptions correspond
17 | * to a server error (HTTP 500). Override this method if you want your exception to generate a
18 | * different status code.
19 | * @return int The associated status code.
20 | */
21 | public function getAssociatedStatusCode()
22 | {
23 | return AbstractResponse::RESPONSE_SERVER_ERROR;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/MethodNotAllowedException.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class MethodNotAllowedException extends Exception implements RouterExceptionInterface
14 | {
15 | // as per RFC2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6)
16 | // we must specify a comma separated list of allowed methods if we return
17 | // a 405 response
18 | private $allowedMethods;
19 |
20 | /**
21 | * Constructor for the method.
22 | * @param string $message The error message string.
23 | * @param array $allowedMethods The array of methods that are allowed.
24 | */
25 | public function __construct($message, $allowedMethods)
26 | {
27 | parent::__construct($message);
28 | $this->allowedMethods = $allowedMethods;
29 | }
30 |
31 | /**
32 | * Returns the associated status code with the exception. By default, most exceptions correspond
33 | * to a server error (HTTP 500). Override this method if you want your exception to generate a
34 | * different status code.
35 | * @return int The associated status code.
36 | */
37 | public function getAssociatedStatusCode()
38 | {
39 | if (!empty($this->allowedMethods) && is_array($this->allowedMethods)) {
40 | @header(sprintf('Allow: %s', implode(',', $this->allowedMethods)));
41 | }
42 | return AbstractResponse::RESPONSE_METHOD_NOT_ALLOWED;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/PluginException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class PluginException extends InternalErrorException
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/ResourceNotFoundException.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class ResourceNotFoundException extends Exception implements RouterExceptionInterface
15 | {
16 | /**
17 | * Returns the associated status code with the exception.
18 | * @return int The associated status code.
19 | */
20 | public function getAssociatedStatusCode()
21 | {
22 | return AbstractResponse::RESPONSE_NOT_FOUND;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/RouterExceptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface RouterExceptionInterface
11 | {
12 | /**
13 | * Returns the associated status code with the exception.
14 | * @return int The associated status code.
15 | */
16 | public function getAssociatedStatusCode();
17 | }
18 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Exception/UnauthorizedException.php:
--------------------------------------------------------------------------------
1 |
14 | * @author Dan Bruce
15 | */
16 | class UnauthorizedException extends Exception implements RouterExceptionInterface
17 | {
18 | /**
19 | * Gets the status code that corresponds to this exception. This is usually
20 | * an HTTP status code.
21 | *
22 | * @return int The status code associated with this exception.
23 | */
24 | public function getAssociatedStatusCode()
25 | {
26 | return AbstractResponse::RESPONSE_UNAUTHORIZED;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/AbstractCliHandler.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | abstract class AbstractCliHandler extends AbstractHandler
14 | {
15 | /**
16 | * Constructor for the class.
17 | *
18 | * @param array $options An array of options for the plugin.
19 | * @throws PluginException
20 | */
21 | public function __construct($options)
22 | {
23 | if (isset($options[Config::KEY_TASKS])) {
24 | $options[Config::KEY_CONTROLLERS] = $options[Config::KEY_TASKS];
25 | unset($options[Config::KEY_TASKS]);
26 | }
27 | parent::__construct($options);
28 | }
29 |
30 | /**
31 | * Determines whether the current handler is appropriate for the given
32 | * path components.
33 | * @param array $components The path components as an array.
34 | * @return boolean Returns true if the handler is appropriate and false otherwise.
35 | */
36 | abstract public function isAppropriate($components);
37 |
38 | /**
39 | * Returns whether a handler should function in a CLI environment.
40 | * @return bool Returns true if the handler should function in a CLI
41 | * environment and false otherwise.
42 | */
43 | public function isCliHandler()
44 | {
45 | return true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/AbstractHandler.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | abstract class AbstractHandler implements DiProviderInterface
20 | {
21 | /** An array of handler-specific options */
22 | protected $options;
23 |
24 | /** A sorted array of handler plugins */
25 | private $plugins;
26 |
27 | /** The service provider to use */
28 | private $serviceProvider;
29 |
30 | /**
31 | * Constructor for the class.
32 | *
33 | * @param array $options An array of options for the plugin.
34 | * @throws PluginException
35 | */
36 | public function __construct($options)
37 | {
38 | $this->options = $options;
39 | $this->plugins = [];
40 | if (isset($options[Config::KEY_PLUGINS])) {
41 | $this->setPlugins((array)$options[Config::KEY_PLUGINS]);
42 | }
43 | // configure the service provider
44 | $services = [];
45 | if (isset($options[Config::KEY_CONTROLLERS])) {
46 | $services = (array)$options[Config::KEY_CONTROLLERS];
47 | }
48 | $this->serviceProvider = new ServiceProvider($services);
49 | if (isset($options[Config::KEY_NAMESPACES])) {
50 | // namespace provisioning
51 | $this->serviceProvider->setNamespaces((array)$options[Config::KEY_NAMESPACES]);
52 | } elseif (isset($options[Config::KEY_FOLDERS])) {
53 | // folder provisioning
54 | $this->serviceProvider->setFolders((array)$options[Config::KEY_FOLDERS]);
55 | }
56 | }
57 |
58 | /**
59 | * Performs the actual routing.
60 | * @return mixed Returns the result of the route.
61 | */
62 | abstract public function performRoute();
63 |
64 | /**
65 | * Retrieve an element from the DI container.
66 | *
67 | * @param string $key The DI key.
68 | * @param boolean $useCache (optional) An optional indicating whether we
69 | * should use the cached version of the element (true by default).
70 | * @return mixed Returns the DI element mapped to that key.
71 | * @throws Exception
72 | */
73 | public function get($key, $useCache = true)
74 | {
75 | return Di::getDefault()->get($key, $useCache);
76 | }
77 |
78 | /**
79 | * Sets an element in the DI container for the specified key.
80 | * @param string $key The DI key.
81 | * @param mixed $element The DI element to store.
82 | * @return Di Returns the Di instance.
83 | */
84 | public function set($key, $element)
85 | {
86 | return Di::getDefault()->set($key, $element);
87 | }
88 |
89 | /**
90 | * Returns the active service provider for this handler.
91 | * @return ServiceProvider The active service provider for this handler.
92 | */
93 | public function getServiceProvider()
94 | {
95 | return $this->serviceProvider;
96 | }
97 |
98 | /**
99 | * Returns the array of plugins registered with this handler.
100 | */
101 | public function getPlugins()
102 | {
103 | return $this->plugins;
104 | }
105 |
106 | /**
107 | * Sets the current list of plugins.
108 | *
109 | * @param array $plugins The array of plugins.
110 | * @return AbstractHandler Returns $this.
111 | * @throws PluginException
112 | */
113 | public function setPlugins($plugins)
114 | {
115 | $this->plugins = [];
116 | foreach ($plugins as $key => $plugin) {
117 | $pluginClass = $plugin;
118 | if (is_array($plugin)) {
119 | if (!isset($plugin[Config::KEY_CLASS])) {
120 | throw new PluginException('Invalid or missing class for plugin '.$key);
121 | } elseif (!class_exists($plugin[Config::KEY_CLASS])) {
122 | throw new PluginException('Invalid or missing class for plugin '.$key);
123 | }
124 | $pluginClass = $plugin[Config::KEY_CLASS];
125 | }
126 | $options = [];
127 | if (isset($plugin[Config::KEY_OPTIONS])) {
128 | $options = (array)$plugin[Config::KEY_OPTIONS];
129 | }
130 | $this->plugins[] = new $pluginClass($options);
131 | }
132 | $this->plugins = $this->sortPlugins($this->plugins);
133 | return $this;
134 | }
135 |
136 | /**
137 | * Sorts the list of plugins according to their execution order
138 | *
139 | * @param array $plugins
140 | * @return array
141 | */
142 | private function sortPlugins($plugins)
143 | {
144 | usort($plugins, function($a, $b) {
145 | return $a->getExecutionOrder() - $b->getExecutionOrder();
146 | });
147 | return $plugins;
148 | }
149 |
150 | /**
151 | * Invokes the plugin hook against all the listed plugins.
152 | * @param string $hook The hook to invoke.
153 | * @param array $args The arguments to pass to the call.
154 | */
155 | public function invokePluginsHook($hook, $args)
156 | {
157 | foreach ($this->getPlugins() as $plugin) {
158 | if (method_exists($plugin, $hook)) {
159 | call_user_func_array([$plugin, $hook], $args);
160 | }
161 | }
162 | }
163 |
164 | /**
165 | * Returns whether a handler should function in a CLI environment.
166 | * @return bool Returns true if the handler should function in a CLI
167 | * environment and false otherwise.
168 | */
169 | abstract public function isCliHandler();
170 |
171 | /**
172 | * Returns the active response encoder.
173 | * @return EncoderInterface Returns the response encoder.
174 | */
175 | public function getEncoder()
176 | {
177 | return new NullEncoder();
178 | }
179 |
180 | /**
181 | * Provides the handler with an opportunity to perform any last minute
182 | * error handling logic. The returned value will be serialized by the
183 | * handler's encoder.
184 | * @param Exception $e The exception that was thrown.
185 | * @return string Returns a serializable value that will be encoded and returned to the client.
186 | */
187 | public function handleException(Exception $e)
188 | {
189 | return $e->getMessage();
190 | }
191 |
192 | /**
193 | * Returns the array of options.
194 | * @return array $options The array of options.
195 | */
196 | public function getOptions()
197 | {
198 | return $this->options;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/AbstractRequestHandler.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | abstract class AbstractRequestHandler extends AbstractHandler
17 | {
18 | /**
19 | * Returns true if the handler determines it should handle this request and false otherwise.
20 | * @param string $path The URL path for the request.
21 | * @param array $query The query parameters.
22 | * @param array $post The post data.
23 | * @param string $verb The HTTP verb used in the request.
24 | * @return boolean Returns true if this handler will handle the request and false otherwise.
25 | */
26 | abstract public function isAppropriate($path, $query, $post, $verb);
27 |
28 | /**
29 | * Returns a request object extracted from the request details (path, query, etc). The method
30 | * isAppropriate() must have returned true, otherwise this method should return null.
31 | * @return HttpRequest|null Returns a Request object or null if this handler is not appropriate.
32 | */
33 | abstract public function getRequest();
34 |
35 | /**
36 | * Provides the handler with an opportunity to perform any last minute
37 | * error handling logic. The returned value will be serialized by the
38 | * handler's encoder.
39 | *
40 | * @param Exception $e The exception that was thrown.
41 | * @return mixed Returns a serializable value that will be encoded and returned to the client.
42 | * @throws Exception
43 | */
44 | public function handleException(Exception $e)
45 | {
46 | $responseCode = AbstractResponse::RESPONSE_SERVER_ERROR;
47 | if ($e instanceof RouterExceptionInterface) {
48 | $responseCode = $e->getAssociatedStatusCode();
49 | }
50 | if (!headers_sent()) {
51 | http_response_code($responseCode);
52 | }
53 | return parent::handleException($e);
54 | }
55 |
56 | /**
57 | * Returns whether a handler should function in a CLI environment.
58 | * @return bool Returns true if the handler should function in a CLI
59 | * environment and false otherwise.
60 | */
61 | public function isCliHandler()
62 | {
63 | return false;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/BatchRequestHandlerInterface.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | interface BatchRequestHandlerInterface
12 | {
13 | /**
14 | * Returns an array of batched requests.
15 | * @return array An array of batched requests.
16 | */
17 | public function getRequests();
18 | }
19 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/CliTaskHandler.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class CliTaskHandler extends AbstractCliHandler
15 | {
16 | /**
17 | * Determines whether the current handler is appropriate for the given
18 | * path components.
19 | * @param array $components The path components as an array.
20 | * @return boolean Returns true if the handler is appropriate and false otherwise.
21 | */
22 | public function isAppropriate($components)
23 | {
24 | $components = array_values(array_filter(array_map('trim', $components), 'strlen'));
25 | $this->options = [];
26 | if (count($components) < 5) {
27 | return false;
28 | }
29 |
30 | if ($components[1] !== '--task' || $components[3] !== '--action') {
31 | return false;
32 | }
33 | $this->options['task'] = $components[2];
34 | $this->options['action'] = $components[4];
35 |
36 | try {
37 | // ensure we have this task registered
38 | $this->getServiceProvider()->get($this->options['task']);
39 | } catch (Exception $e) {
40 | return false;
41 | }
42 |
43 | return true;
44 | }
45 |
46 | /**
47 | * Performs the actual routing.
48 | *
49 | * @return mixed Returns the result of the route.
50 | * @throws ResourceNotFoundException
51 | * @throws Exception
52 | */
53 | public function performRoute()
54 | {
55 | $task = $this->getServiceProvider()->getServiceInstance($this->options['task']);
56 | if (false === method_exists($task, $this->options['action'])) {
57 | throw new ResourceNotFoundException(
58 | sprintf(
59 | '%s task does not have action %s.',
60 | $this->options['task'],
61 | $this->options['action']
62 | )
63 | );
64 | }
65 |
66 | // call the task's init function
67 | if ($task instanceof TaskInterface) {
68 | $task->init($this->options);
69 | }
70 |
71 | $taskParams = array_splice($_SERVER['argv'], 5);
72 | $action = $this->options['action'];
73 | return $task->$action($taskParams);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/DirectScriptHandler.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class DirectScriptHandler extends AbstractRequestHandler
14 | {
15 | /** Options key for the path mapping array */
16 | const KEY_PATH_MAP = 'pathMap';
17 |
18 | private $scriptPath;
19 |
20 | /**
21 | * Returns true if the handler determines it should handle this request and false otherwise.
22 | * @param string $path The URL path for the request.
23 | * @param array $query The query parameters.
24 | * @param array $post The post data.
25 | * @param string $verb The HTTP verb used in the request.
26 | * @return boolean Returns true if this handler will handle the request and false otherwise.
27 | */
28 | public function isAppropriate($path, $query, $post, $verb)
29 | {
30 | $options = $this->getOptions();
31 | $pathMaps = [];
32 | if (isset($options[self::KEY_PATH_MAP])) {
33 | $pathMaps = (array)$options[self::KEY_PATH_MAP];
34 | }
35 | foreach ($pathMaps as $pathPrefix => $folder) {
36 | if (false !== ($pos = strpos($path, $pathPrefix))) {
37 | $scriptPath = $folder.DIRECTORY_SEPARATOR.substr($path, $pos + strlen($pathPrefix));
38 | if (file_exists($scriptPath) && is_readable($scriptPath)) {
39 | $this->scriptPath = realpath($scriptPath);
40 | return true;
41 | }
42 | }
43 | }
44 | return false;
45 | }
46 |
47 | /**
48 | * Returns a request object extracted from the request details (path, query, etc). The method
49 | * isAppropriate() must have returned true, otherwise this method should return null.
50 | * @return HttpRequest|null Returns a Request object or null if this handler is not appropriate.
51 | */
52 | public function getRequest()
53 | {
54 | return null;
55 | }
56 |
57 | /**
58 | * Performs the actual routing.
59 | * @return string Returns the result of the route.
60 | */
61 | public function performRoute()
62 | {
63 | ob_start();
64 | require $this->scriptPath;
65 | return ob_get_clean();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/PatternMatchHandler.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class PatternMatchHandler extends AbstractRequestHandler
18 | {
19 | /** the config key for the list of routes */
20 | const KEY_ROUTES = 'routes';
21 |
22 | /** the config key for the route cache */
23 | const KEY_CACHE = 'routeCache';
24 |
25 | // the currently active callback
26 | private $callback;
27 | // the currently active route parameters
28 | private $routeParams;
29 |
30 | /** All supported HTTP verbs */
31 | private static $allHttpVerbs = [
32 | 'GET',
33 | 'POST',
34 | 'PUT',
35 | 'DELETE',
36 | 'OPTIONS'
37 | ];
38 |
39 | /** The route information from FastRoute */
40 | private $routeInfo;
41 |
42 | /**
43 | * Returns true if the handler determines it should handle this request and false otherwise.
44 | * @param string $path The URL path for the request.
45 | * @param array $query The query parameters.
46 | * @param array $post The post data.
47 | * @param string $verb The HTTP verb used in the request.
48 | * @return boolean Returns true if this handler will handle the request and false otherwise.
49 | */
50 | public function isAppropriate($path, $query, $post, $verb)
51 | {
52 | $routeInfo = $this->getRouteInfo($verb, $path);
53 | if (Dispatcher::FOUND !== $routeInfo[0]) {
54 | return false;
55 | }
56 | $this->callback = $routeInfo[1];
57 | $this->routeParams = $routeInfo[2] ?? [];
58 | return true;
59 | }
60 |
61 | /**
62 | * Returns the array of route info from the routing library.
63 | * @param string $verb The HTTP verb used in the request.
64 | * @param string $path The path to match against the patterns.
65 | * @param boolean $useCache (optional) An optional flag whether to use the
66 | * cached route info or not. Defaults to false.
67 | * @return array Returns the route info as an array.
68 | */
69 | protected function getRouteInfo($verb, $path, $useCache = false)
70 | {
71 | if (!$useCache || !isset($this->routeInfo)) {
72 | $dispatcher = $this->getDispatcher($this->getRoutes());
73 | $this->routeInfo = $dispatcher->dispatch(strtoupper($verb), $path);
74 | }
75 | return $this->routeInfo;
76 | }
77 |
78 | /**
79 | * Returns the array of routes.
80 | * @return array The array of routes.
81 | */
82 | protected function getRoutes()
83 | {
84 | $options = $this->getOptions();
85 | return $options[self::KEY_ROUTES] ?? [];
86 | }
87 |
88 | /**
89 | * Performs the actual routing.
90 | * @return mixed Returns the result of the route.
91 | */
92 | public function performRoute()
93 | {
94 | return call_user_func($this->callback, $this->routeParams);
95 | }
96 |
97 | /**
98 | * Returns a request object extracted from the request details (path, query, etc). The method
99 | * isAppropriate() must have returned true, otherwise this method should return null.
100 | * @return HttpRequest|null Returns a Request object or null if this handler is not appropriate.
101 | */
102 | public function getRequest()
103 | {
104 | return null;
105 | }
106 |
107 | /**
108 | * Returns an instance of the FastRoute dispatcher.
109 | * @param array $routes The array of specified routes.
110 | * @return Dispatcher The dispatcher to use.
111 | */
112 | private function getDispatcher($routes)
113 | {
114 | $verbs = self::$allHttpVerbs;
115 | $f = function(RouteCollector $collector) use ($routes, $verbs) {
116 | foreach ($routes as $pattern => $route) {
117 | if (is_array($route)) {
118 | foreach ($route as $verb => $callback) {
119 | $collector->addRoute(strtoupper($verb), $pattern, $callback);
120 | }
121 | } else {
122 | foreach ($verbs as $verb) {
123 | $collector->addRoute($verb, $pattern, $route);
124 | }
125 | }
126 | }
127 | };
128 |
129 | $options = $this->getOptions();
130 | $cacheData = [];
131 | if (isset($options[self::KEY_CACHE])) {
132 | $cacheData = (array)$options[self::KEY_CACHE];
133 | }
134 |
135 | if (empty($cacheData)) {
136 | return simpleDispatcher($f);
137 | }
138 | return cachedDispatcher($f, $cacheData);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Handler/RestHandler.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class RestHandler extends ControllerHandler
14 | {
15 | /** Constants indicating the type of route */
16 | const MATCHES_ID = 8;
17 | const MATCHES_CONTROLLER_AND_ID = 9;
18 | const MATCHES_CONTROLLER_ACTION_AND_ID = 11;
19 |
20 | /** API version pattern */
21 | const ROUTE_PATTERN_VERSION_ONE = 'v{version:\d+}';
22 | const ROUTE_PATTERN_VERSION_TWO = 'v{version:\d+\.\d+}';
23 | const ROUTE_PATTERN_VERSION_THREE = 'v{version:\d+\.\d+\.\d+}';
24 |
25 | /** object ID version pattern */
26 | const ROUTE_PATTERN_OBJECT_ID = '{objectId:\d+}';
27 |
28 | /**
29 | * Returns true if the handler determines it should handle this request and false otherwise.
30 | *
31 | * @param string $path The URL path for the request.
32 | * @param array $query The query parameters.
33 | * @param array $post The post data.
34 | * @param string $verb The HTTP verb used in the request.
35 | * @return boolean Returns true if this handler will handle the request and false otherwise.
36 | * @throws InternalErrorException
37 | */
38 | public function isAppropriate($path, $query, $post, $verb)
39 | {
40 | // use the parent method to match the routes
41 | if (false === parent::isAppropriate($path, $query, $post, $verb)) {
42 | return false;
43 | }
44 |
45 | // determine the route information from the path
46 | $routeInfo = $this->getRouteInfo($verb, $path, true);
47 | $this->routeParams = [$routeInfo[2]['version']];
48 | if ($routeInfo[1] & self::MATCHES_ID) {
49 | $this->routeParams[] = intval($routeInfo[2]['objectId']);
50 | }
51 |
52 | // use JSON encoder by default
53 | $this->encoder = new JsonEncoder();
54 |
55 | return true;
56 | }
57 |
58 | /**
59 | * Returns the array of routes.
60 | * @return array The array of routes.
61 | */
62 | protected function getRoutes()
63 | {
64 | $c = parent::ROUTE_PATTERN_CONTROLLER;
65 | $a = parent::ROUTE_PATTERN_ACTION;
66 | $v1 = self::ROUTE_PATTERN_VERSION_ONE;
67 | $v2 = self::ROUTE_PATTERN_VERSION_TWO;
68 | $v3 = self::ROUTE_PATTERN_VERSION_THREE;
69 | $o = self::ROUTE_PATTERN_OBJECT_ID;
70 | return [
71 | "/$v1/$c" => self::MATCHES_CONTROLLER,
72 | "/$v1/$c/" => self::MATCHES_CONTROLLER,
73 | "/$v2/$c" => self::MATCHES_CONTROLLER,
74 | "/$v2/$c/" => self::MATCHES_CONTROLLER,
75 | "/$v3/$c" => self::MATCHES_CONTROLLER,
76 | "/$v3/$c/" => self::MATCHES_CONTROLLER,
77 | "/$v1/$c/$a" => self::MATCHES_CONTROLLER_AND_ACTION,
78 | "/$v1/$c/$a/" => self::MATCHES_CONTROLLER_AND_ACTION,
79 | "/$v2/$c/$a" => self::MATCHES_CONTROLLER_AND_ACTION,
80 | "/$v2/$c/$a/" => self::MATCHES_CONTROLLER_AND_ACTION,
81 | "/$v3/$c/$a" => self::MATCHES_CONTROLLER_AND_ACTION,
82 | "/$v3/$c/$a/" => self::MATCHES_CONTROLLER_AND_ACTION,
83 | "/$v1/$c/$o" => self::MATCHES_CONTROLLER_AND_ID,
84 | "/$v1/$c/$o/" => self::MATCHES_CONTROLLER_AND_ID,
85 | "/$v2/$c/$o" => self::MATCHES_CONTROLLER_AND_ID,
86 | "/$v2/$c/$o/" => self::MATCHES_CONTROLLER_AND_ID,
87 | "/$v3/$c/$o" => self::MATCHES_CONTROLLER_AND_ID,
88 | "/$v3/$c/$o/" => self::MATCHES_CONTROLLER_AND_ID,
89 | "/$v1/$c/$a/$o" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
90 | "/$v1/$c/$a/$o/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
91 | "/$v1/$c/$o/$a" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
92 | "/$v1/$c/$o/$a/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
93 | "/$v2/$c/$a/$o" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
94 | "/$v2/$c/$a/$o/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
95 | "/$v2/$c/$o/$a" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
96 | "/$v2/$c/$o/$a/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
97 | "/$v3/$c/$a/$o" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
98 | "/$v3/$c/$a/$o/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
99 | "/$v3/$c/$o/$a" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
100 | "/$v3/$c/$o/$a/" => self::MATCHES_CONTROLLER_ACTION_AND_ID,
101 | ];
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/AbstractControllerPlugin.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | abstract class AbstractControllerPlugin extends AbstractPlugin implements ControllerPluginInterface
15 | {
16 | /**
17 | * Invoked before the handler decides which controller will be used.
18 | * @param AbstractHandler $handler The handler selected by the router.
19 | * @param HttpRequest $request The request to be handled.
20 | */
21 | public function beforeControllerSelected(
22 | AbstractHandler $handler,
23 | HttpRequest $request
24 | ) {
25 | }
26 |
27 | /**
28 | * Invoked after the router has decided which controller will be used.
29 | * @param AbstractHandler $handler The handler selected by the router.
30 | * @param HttpRequest $request The request to be handled.
31 | * @param AbstractController $controller The controller determined to be used.
32 | * @param string $action The name of the action that will be invoked.
33 | */
34 | public function afterControllerSelected(
35 | AbstractHandler $handler,
36 | HttpRequest $request,
37 | AbstractController $controller,
38 | $action
39 | ) {
40 | }
41 |
42 | /**
43 | * Invoked before the handler invokes the selected action.
44 | * @param AbstractHandler $handler The handler selected by the router.
45 | * @param HttpRequest $request The request to be handled.
46 | * @param AbstractController $controller The controller determined to be used.
47 | * @param string $action The name of the action that will be invoked.
48 | */
49 | public function beforeActionInvoked(
50 | AbstractHandler $handler,
51 | HttpRequest $request,
52 | AbstractController $controller,
53 | $action
54 | ) {
55 | }
56 |
57 | /**
58 | * Invoked after the handler invoked the selected action.
59 | * @param AbstractHandler $handler The handler selected by the router.
60 | * @param HttpRequest $request The request to be handled.
61 | * @param AbstractController $controller The controller determined to be used.
62 | * @param string $action The name of the action that will be invoked.
63 | * @param mixed $response The response from the controller action.
64 | */
65 | public function afterActionInvoked(
66 | AbstractHandler $handler,
67 | HttpRequest $request,
68 | AbstractController $controller,
69 | $action,
70 | $response
71 | ) {
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/AbstractPlugin.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | abstract class AbstractPlugin implements PluginInterface, DiProviderInterface
17 | {
18 | /** the default priority of a plugin */
19 | const PRIORITY_DEFAULT = 1000;
20 |
21 | /** A string constant indicating the whitelist/blacklist applies to all
22 | actions within a controller */
23 | const ALL_ACTIONS = 'all';
24 |
25 | /** The plugin options */
26 | protected $options;
27 |
28 | // properties for plugin/service compatibility
29 | // both properties cannot be set at the same time (one or the other or both
30 | // must be null at any point)
31 | private $whitelist;
32 | private $blacklist;
33 |
34 | /**
35 | * Constructor for the plugin.
36 | * @param array $options The array of options.
37 | */
38 | public function __construct($options)
39 | {
40 | $this->options = $options;
41 | }
42 |
43 | /**
44 | * Invoked directly after the router decides which handler will be used.
45 | * @param AbstractHandler $handler The handler selected by the router.
46 | */
47 | public function afterHandlerSelected(AbstractHandler $handler)
48 | {
49 | }
50 |
51 | /**
52 | * Invoked after the entire route has been handled.
53 | * @param AbstractHandler $handler The handler selected by the router.
54 | */
55 | public function afterFullRouteInvoked(AbstractHandler $handler)
56 | {
57 | }
58 |
59 | /**
60 | * Invoked if an exception is thrown during the route.
61 | * @param AbstractHandler $handler The handler selected by the router.
62 | * @param Exception $exception The exception that was thrown.
63 | */
64 | public function errorOccurred(AbstractHandler $handler, Exception $exception)
65 | {
66 | }
67 |
68 | /**
69 | * Returns a sortable number for sorting plugins by execution priority. A lower number indicates
70 | * higher priority.
71 | * @return integer The execution priority (as a number).
72 | */
73 | public function getExecutionOrder()
74 | {
75 | return self::PRIORITY_DEFAULT;
76 | }
77 |
78 | /**
79 | * Sets the controller/action whitelist of this particular plugin. Note that
80 | * setting a whitelist will remove any previously set blacklists.
81 | * @param array $whitelist The controller/action whitelist.
82 | * @return self Returns $this.
83 | */
84 | public function setWhitelist($whitelist)
85 | {
86 | $this->whitelist = $whitelist;
87 | $this->blacklist = null;
88 | return $this;
89 | }
90 |
91 | /**
92 | * Sets the controller/action blacklist of this particular plugin. Note that
93 | * setting a blacklist will remove any previously set whitelists.
94 | * @param array $blacklist The controller/action blacklist.
95 | * @return self Returns $this.
96 | */
97 | public function setBlacklist($blacklist)
98 | {
99 | $this->whitelist = null;
100 | $this->blacklist = $blacklist;
101 | return $this;
102 | }
103 |
104 | /**
105 | * Returns whether or not the given controller and action requested should
106 | * invoke this plugin.
107 | * @param string $controller The requested controller.
108 | * @param string $action The requested action.
109 | * @return boolean Returns true if the given plugin is allowed to run against
110 | * this controller/action and false otherwise.
111 | */
112 | public function supportsControllerAndAction($controller, $action)
113 | {
114 | if (null === $this->blacklist) {
115 | if (null === $this->whitelist) {
116 | // plugin has global scope
117 | return true;
118 | }
119 | // we use a whitelist so ensure the controller is in the whitelist
120 | if (!isset($this->whitelist[$controller])) {
121 | return false;
122 | }
123 | // the whitelisted controller could be an array of actions or it could
124 | // be mapped to the "all" string
125 | if (is_array($this->whitelist[$controller])) {
126 | return in_array($action, $this->whitelist[$controller]);
127 | }
128 | return self::ALL_ACTIONS === (string)$this->whitelist[$controller];
129 | }
130 |
131 | // if the controller isn't in the blacklist at all, we're good
132 | if (!isset($this->blacklist[$controller])) {
133 | return true;
134 | }
135 |
136 | // if the controller is not an array we return false
137 | // otherwise we check if the action is listed in the array
138 | return is_array($this->blacklist[$controller]) &&
139 | !in_array($action, $this->blacklist[$controller]);
140 | }
141 |
142 | /**
143 | * Retrieve an element from the DI container.
144 | *
145 | * @param string $key The DI key.
146 | * @param boolean $useCache (optional) An optional indicating whether we
147 | * should use the cached version of the element (true by default).
148 | * @return mixed Returns the DI element mapped to that key.
149 | * @throws Exception
150 | */
151 | public function get($key, $useCache = true)
152 | {
153 | return Di::getDefault()->get($key, $useCache);
154 | }
155 |
156 | /**
157 | * Sets an element in the DI container for the specified key.
158 | * @param string $key The DI key.
159 | * @param mixed $element The DI element to store.
160 | * @return Di Returns the Di instance.
161 | */
162 | public function set($key, $element)
163 | {
164 | return Di::getDefault()->set($key, $element);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/Authentication/AbstractAuthenticationPlugin.php:
--------------------------------------------------------------------------------
1 |
18 | * @author Dan Bruce
19 | */
20 | abstract class AbstractAuthenticationPlugin extends AbstractPlugin
21 | {
22 | /**
23 | * The default dependency injection key for the authentication mechanism.
24 | *
25 | * This also serves as the name of the DI key option, for consistency.
26 | */
27 | const DI_KEY_AUTH = 'AuthMechanism';
28 |
29 | /** The key for fetching the authentication mechanism from dependency
30 | injection. */
31 | protected $authKey = self::DI_KEY_AUTH;
32 |
33 | /**
34 | * Constructor for the class.
35 | *
36 | * @param array $options An array of options for the plugin.
37 | */
38 | public function __construct($options)
39 | {
40 | parent::__construct($options);
41 |
42 | if (isset($options[self::DI_KEY_AUTH])) {
43 | $this->authKey = $options[self::DI_KEY_AUTH];
44 | }
45 | }
46 |
47 | /**
48 | * Invoked directly after the router decides which handler will be used.
49 | *
50 | * @param AbstractHandler $handler The handler selected by the router.
51 | * @throws InternalErrorException
52 | * @throws UnauthorizedException
53 | */
54 | public function afterHandlerSelected(AbstractHandler $handler)
55 | {
56 | parent::afterHandlerSelected($handler);
57 |
58 | $auth = $this->get($this->authKey);
59 | if (!($auth instanceof AuthenticatorInterface)) {
60 | throw new InternalErrorException(sprintf(
61 | "Implementation of AuthenticationInterface required. Please check your %s configuration.",
62 | $this->authKey
63 | ));
64 | }
65 |
66 | if (!($credentials = $this->getCredentials())) {
67 | throw new UnauthorizedException("Authentication is required to access this resource.");
68 | }
69 |
70 | if (!$auth->authenticate($credentials)) {
71 | throw new UnauthorizedException("Authentication is required to access this resource.");
72 | }
73 | }
74 |
75 | /**
76 | * Extract credentials from the request.
77 | *
78 | * @return mixed An array of credentials; A username and password pair, or false if credentials aren't available
79 | */
80 | abstract public function getCredentials();
81 | }
82 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/Authentication/HttpBasicAuthenticationPlugin.php:
--------------------------------------------------------------------------------
1 |
16 | * @author Dan Bruce
17 | */
18 | class HttpBasicAuthenticationPlugin extends AbstractAuthenticationPlugin
19 | {
20 | /** The authentication realm, usually presented to the user in a
21 | username/password dialog box. */
22 | private $realm = "Authentication Required";
23 |
24 | /**
25 | * Create a new HTTP/Basic Authentication plugin.
26 | *
27 | * @param array $options An associative array of options. Supports AuthMechanism and realm options.
28 | */
29 | public function __construct($options)
30 | {
31 | parent::__construct($options);
32 |
33 | if (!empty($options['realm'])) {
34 | $this->realm = $options['realm'];
35 | }
36 | }
37 |
38 | /**
39 | * Invoked directly after the router decides which handler will be used.
40 | *
41 | * @param AbstractHandler $handler The handler selected by the router.
42 | * @throws UnauthorizedException
43 | * @throws InternalErrorException
44 | */
45 | public function afterHandlerSelected(AbstractHandler $handler)
46 | {
47 | try {
48 | parent::afterHandlerSelected($handler);
49 | } catch (UnauthorizedException $e) {
50 | @header(sprintf('WWW-Authenticate: Basic realm="%s"', $this->realm));
51 | throw $e;
52 | }
53 | }
54 |
55 | /**
56 | * Extract credentials from the request, PHP's PHP_AUTH_(USER|PW) server variables in this case.
57 | *
58 | * @return mixed An array of credentials; A username and password pair, or false if credentials aren't available
59 | */
60 | public function getCredentials()
61 | {
62 | if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
63 | return [$_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']];
64 | }
65 | return false;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/ControllerPluginInterface.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | interface ControllerPluginInterface extends PluginInterface
18 | {
19 | /**
20 | * Invoked before the handler decides which controller will be used.
21 | * @param AbstractHandler $handler The handler selected by the router.
22 | * @param HttpRequest $request The request to be handled.
23 | */
24 | public function beforeControllerSelected(
25 | AbstractHandler $handler,
26 | HttpRequest $request
27 | );
28 |
29 | /**
30 | * Invoked after the router has decided which controller will be used.
31 | * @param AbstractHandler $handler The handler selected by the router.
32 | * @param HttpRequest $request The request to be handled.
33 | * @param AbstractController $controller The controller determined to be used.
34 | * @param string $action The name of the action that will be invoked.
35 | */
36 | public function afterControllerSelected(
37 | AbstractHandler $handler,
38 | HttpRequest $request,
39 | AbstractController $controller,
40 | $action
41 | );
42 |
43 | /**
44 | * Invoked before the handler invokes the selected action.
45 | * @param AbstractHandler $handler The handler selected by the router.
46 | * @param HttpRequest $request The request to be handled.
47 | * @param AbstractController $controller The controller determined to be used.
48 | * @param string $action The name of the action that will be invoked.
49 | */
50 | public function beforeActionInvoked(
51 | AbstractHandler $handler,
52 | HttpRequest $request,
53 | AbstractController $controller,
54 | $action
55 | );
56 |
57 | /**
58 | * Invoked after the handler invoked the selected action.
59 | * @param AbstractHandler $handler The handler selected by the router.
60 | * @param HttpRequest $request The request to be handled.
61 | * @param AbstractController $controller The controller determined to be used.
62 | * @param string $action The name of the action that will be invoked.
63 | * @param mixed $response The response from the controller action.
64 | */
65 | public function afterActionInvoked(
66 | AbstractHandler $handler,
67 | HttpRequest $request,
68 | AbstractController $controller,
69 | $action,
70 | $response
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/HttpHeader/RouterHeaderPlugin.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class RouterHeaderPlugin extends AbstractPlugin
14 | {
15 | /**
16 | * Invoked directly after the router decides which handler will be used.
17 | * @param AbstractHandler $handler The handler selected by the router.
18 | */
19 | public function afterHandlerSelected(AbstractHandler $handler)
20 | {
21 | parent::afterHandlerSelected($handler);
22 | @header('X-Router: SnappyRouter');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Plugin/PluginInterface.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | interface PluginInterface
17 | {
18 | /**
19 | * Invoked directly after the router decides which handler will be used.
20 | * @param AbstractHandler $handler The handler selected by the router.
21 | */
22 | public function afterHandlerSelected(AbstractHandler $handler);
23 |
24 | /**
25 | * Invoked after the entire route has been handled.
26 | * @param AbstractHandler $handler The handler selected by the router.
27 | */
28 | public function afterFullRouteInvoked(AbstractHandler $handler);
29 |
30 | /**
31 | * Invoked if an exception is thrown during the route.
32 | * @param AbstractHandler $handler The handler selected by the router.
33 | * @param Exception $exception The exception that was thrown.
34 | */
35 | public function errorOccurred(AbstractHandler $handler, Exception $exception);
36 |
37 | /**
38 | * Returns a sortable number for sorting plugins by execution priority. A lower number indicates
39 | * higher priority.
40 | * @return integer The execution priority (as a number).
41 | */
42 | public function getExecutionOrder();
43 |
44 | /**
45 | * Sets the controller/action whitelist of this particular plugin. Note that
46 | * setting a whitelist will remove any previously set blacklists.
47 | * @param array $whitelist The controller/action whitelist.
48 | * @return self Returns $this.
49 | */
50 | public function setWhitelist($whitelist);
51 |
52 | /**
53 | * Sets the controller/action blacklist of this particular plugin. Note that
54 | * setting a blacklist will remove any previously set whitelists.
55 | * @param array $blacklist The controller/action blacklist.
56 | * @return self Returns $this.
57 | */
58 | public function setBlacklist($blacklist);
59 |
60 | /**
61 | * Returns whether or not the given controller and action requested should
62 | * invoke this plugin.
63 | * @param string $controller The requested controller.
64 | * @param string $action The requested action.
65 | * @return boolean Returns true if the given plugin is allowed to run against
66 | * this controller/action and false otherwise.
67 | */
68 | public function supportsControllerAndAction($controller, $action);
69 | }
70 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Request/AbstractRequest.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class AbstractRequest implements RequestInterface
11 | {
12 | /** The controller to use in the request. */
13 | private $controller;
14 | /** The action to invoke in the request. */
15 | private $action;
16 |
17 | /**
18 | * Constructor for the abstract request.
19 | * @param string $controller The controller to be used.
20 | * @param string $action The action to be invoked.
21 | */
22 | public function __construct($controller, $action)
23 | {
24 | $this->setController($controller);
25 | $this->setAction($action);
26 | }
27 |
28 | /**
29 | * Returns the controller to be used in the request.
30 | * @return string Returns the controller DI key to be used in the request.
31 | */
32 | public function getController()
33 | {
34 | return $this->controller;
35 | }
36 |
37 | /**
38 | * Sets the controller to be used in the request.
39 | * @param string $controller The controller DI key to be used in the request.
40 | * @return RequestInterface Returns $this.
41 | */
42 | public function setController($controller)
43 | {
44 | $this->controller = $controller;
45 | return $this;
46 | }
47 |
48 | /**
49 | * Returns the action to be invoked as a string.
50 | * @return string The action to be invoked.
51 | */
52 | public function getAction()
53 | {
54 | return $this->action;
55 | }
56 |
57 | /**
58 | * Sets the action to be invoked by the request
59 | * @param string $action The action to be invoked by the request.
60 | * @return RequestInterface Returns $this.
61 | */
62 | public function setAction($action)
63 | {
64 | $this->action = (string)$action;
65 | return $this;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Request/HttpRequestInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface HttpRequestInterface
11 | {
12 | /**
13 | * Returns the HTTP verb used in the request.
14 | * @return string The HTTP verb used in the request.
15 | */
16 | public function getVerb();
17 |
18 | /**
19 | * Sets the HTTP verb used in the request.
20 | * @param string $verb The HTTP verb used in the request.
21 | * @return HttpRequestInterface Returns $this.
22 | */
23 | public function setVerb($verb);
24 |
25 | /**
26 | * Returns the GET data parameter associated with the specified key.
27 | * @param string $param The GET data parameter.
28 | * @param mixed $defaultValue The default value to use when the key is not present.
29 | * @param array $filters The array of filters to apply to the data.
30 | * @return mixed Returns the data from the GET parameter after being filtered (or
31 | * the default value if the parameter is not present)
32 | */
33 | public function getQuery($param, $defaultValue = null, $filters = []);
34 |
35 | /**
36 | * Sets all the QUERY data for the current request.
37 | * @param array $queryData The query data for the current request.
38 | * @return HttpRequestInterface Returns $this.
39 | */
40 | public function setQuery($queryData);
41 |
42 | /**
43 | * Returns the POST data parameter associated with the specified key.
44 | * @param string $param The POST data parameter.
45 | * @param mixed $defaultValue The default value to use when the key is not present.
46 | * @param array $filters The array of filters to apply to the data.
47 | * @return mixed Returns the data from the POST parameter after being filtered (or
48 | * the default value if the parameter is not present)
49 | */
50 | public function getPost($param, $defaultValue = null, $filters = []);
51 |
52 | /**
53 | * Sets all the POST data for the current request.
54 | * @param array $postData The post data for the current request.
55 | * @return HttpRequestInterface Returns $this.
56 | */
57 | public function setPost($postData);
58 |
59 | /**
60 | * Returns an array of all the input parameters from the query and post data.
61 | * @return array An array of all input.
62 | */
63 | public function getAllInput();
64 | }
65 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Request/JsonRpcRequest.php:
--------------------------------------------------------------------------------
1 | method)) {
31 | $action = $payload->method;
32 | }
33 | parent::__construct($controller, $action, $verb);
34 | $this->payload = $payload;
35 | }
36 |
37 | /**
38 | * Returns the POST data parameter associated with the specified key.
39 | *
40 | * Since JSON-RPC and POST'ed data are mutually exclusive this returns null, or the default if provided.
41 | *
42 | * @param string $param The POST data parameter to retrieve.
43 | * @param mixed $defaultValue The default value to use when the key is not present.
44 | * @param mixed $filters The array of filters (or single filter) to apply to the data. Ignored.
45 | * @return mixed Returns null because POST is not possible, or the default value if the parameter is not present.
46 | */
47 | public function getPost($param, $defaultValue = null, $filters = [])
48 | {
49 | return $defaultValue ?? null;
50 | }
51 |
52 | /**
53 | * Get the request version.
54 | *
55 | * @return string The request's version string. "1.0" is assumed if version is not present in the request.
56 | */
57 | public function getVersion()
58 | {
59 | return $this->payload->jsonrpc ?? "1.0";
60 | }
61 |
62 | /**
63 | * Get the request method.
64 | *
65 | * @return string The request method name.
66 | */
67 | public function getMethod()
68 | {
69 | return $this->payload->method;
70 | }
71 |
72 | /**
73 | * Get the request identifier
74 | *
75 | * @return mixed The request identifier. This is generally a string, but the JSON-RPC spec isn't strict.
76 | */
77 | public function getIdentifier()
78 | {
79 | return $this->payload->id ?? null;
80 | }
81 |
82 | /**
83 | * Get request parameters.
84 | *
85 | * Note: Since PHP does not support named params, named params are turned into a single request object parameter.
86 | *
87 | * @return array An array of request paramters
88 | */
89 | public function getParameters()
90 | {
91 | if (isset($this->payload->params)) {
92 | /* JSON-RPC 2 can pass named params. For PHP's sake, turn that into a single object param. */
93 | return is_array($this->payload->params) ? $this->payload->params : [$this->payload->params];
94 | }
95 | return [];
96 | }
97 |
98 | /**
99 | * Returns whether this request is minimally valid for JSON RPC.
100 | * @return bool Returns true if the payload is valid and false otherwise.
101 | */
102 | public function isValid()
103 | {
104 | $action = $this->getAction();
105 | return is_object($this->payload) && !empty($action);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Request/RequestInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface RequestInterface
14 | {
15 | /**
16 | * Returns the controller to be used in the request.
17 | * @return string Returns the controller DI key to be used in the request.
18 | */
19 | public function getController();
20 |
21 | /**
22 | * Sets the controller to be used in the request.
23 | * @param AbstractController $controller The controller to be used in the request.
24 | * @return RequestInterface Returns $this.
25 | */
26 | public function setController($controller);
27 |
28 | /**
29 | * Returns the action to be invoked as a string.
30 | * @return string The action to be invoked.
31 | */
32 | public function getAction();
33 |
34 | /**
35 | * Sets the action to be invoked by the request
36 | * @param string $action The action to be invoked by the request.
37 | * @return RequestInterface Returns $this.
38 | */
39 | public function setAction($action);
40 | }
41 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Response/AbstractResponse.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | abstract class AbstractResponse implements ResponseInterface
12 | {
13 | /** HTTP response code for OK */
14 | const RESPONSE_OK = 200;
15 | /** HTTP response code for a bad request */
16 | const RESPONSE_BAD_REQUEST = 400;
17 | /** HTTP response code for unauthorized */
18 | const RESPONSE_UNAUTHORIZED = 401;
19 | /** HTTP response code for forbidden */
20 | const RESPONSE_FORBIDDEN = 403;
21 | /** HTTP response code for not found */
22 | const RESPONSE_NOT_FOUND = 404;
23 | /** HTTP response code for method not allowed */
24 | const RESPONSE_METHOD_NOT_ALLOWED = 405;
25 | /** HTTP response code for too many requests in a period of time
26 | * See: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429 */
27 | const RESPONSE_RATE_LIMITED = 429;
28 | /** HTTP response code for a server error */
29 | const RESPONSE_SERVER_ERROR = 500;
30 | /** HTTP response code for server unavailable */
31 | const RESPONSE_SERVICE_UNAVAILABLE = 503;
32 |
33 | /**
34 | * Constructor for the response.
35 | * @param mixed $responseObject A response object that can be serialized to a string.
36 | * @param int $statusCode The HTTP response.
37 | */
38 | public function __construct($responseObject, $statusCode = self::RESPONSE_OK)
39 | {
40 | $this->setResponseObject($responseObject);
41 | $this->setStatusCode($statusCode);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Response/JsonRpcResponse.php:
--------------------------------------------------------------------------------
1 | error = (object)[
30 | 'code' => $error->getCode(),
31 | 'message' => $error->getMessage()
32 | ];
33 | } else {
34 | $response->result = $result;
35 | }
36 |
37 | if ($request) {
38 | /* 1.0: omit version, 2.0 and newer, echo back version. */
39 | if ($request->getVersion() != "1.0") {
40 | $response->jsonrpc = $request->getVersion();
41 | }
42 |
43 | /* For notifications (null id), return nothing. Otherwise, pass back the id. */
44 | if ($request->getIdentifier() === null) {
45 | $response = "";
46 | } else {
47 | $response->id = $request->getIdentifier();
48 | }
49 | }
50 |
51 | parent::__construct($response);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Response/Response.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Response extends AbstractResponse
11 | {
12 | private $responseObject; // the serializable response object
13 | private $statusCode; // the http response code
14 |
15 | /**
16 | * Returns the serializable response object.
17 | * @return mixed The serializable response object.
18 | */
19 | public function getResponseObject()
20 | {
21 | return $this->responseObject;
22 | }
23 |
24 | /**
25 | * Sets the serializable response object.
26 | * @param mixed $responseObject The serializable response object.
27 | * @return ResponseInterface Returns $this.
28 | */
29 | public function setResponseObject($responseObject)
30 | {
31 | $this->responseObject = $responseObject;
32 | return $this;
33 | }
34 |
35 | /**
36 | * Returns the HTTP status code associated with this response.
37 | * @return integer The HTTP status code associated with this response.
38 | */
39 | public function getStatusCode()
40 | {
41 | return $this->statusCode;
42 | }
43 |
44 | /**
45 | * Sets the HTTP status code associated with this response.
46 | * @param int $statusCode The HTTP status code associated with this response.
47 | * @return ResponseInterface Returns $this.
48 | */
49 | public function setStatusCode($statusCode)
50 | {
51 | $statusCode = intval($statusCode);
52 | $this->statusCode = ($statusCode > 0) ? $statusCode : self::RESPONSE_OK;
53 | return $this;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Response/ResponseInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface ResponseInterface
11 | {
12 | /**
13 | * Returns the serializable response object.
14 | * @return mixed The serializable response object.
15 | */
16 | public function getResponseObject();
17 |
18 | /**
19 | * Sets the serializable response object.
20 | * @param mixed $responseObject The serializable response object.
21 | * @return ResponseInterface Returns $this.
22 | */
23 | public function setResponseObject($responseObject);
24 |
25 | /**
26 | * Returns the HTTP status code associated with this response.
27 | * @return integer The HTTP status code associated with this response.
28 | */
29 | public function getStatusCode();
30 |
31 | /**
32 | * Sets the HTTP status code associated with this response.
33 | * @param int $statusCode The HTTP status code associated with this response.
34 | * @return ResponseInterface Returns $this.
35 | */
36 | public function setStatusCode($statusCode);
37 | }
38 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Task/AbstractTask.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class AbstractTask implements DiProviderInterface, TaskInterface
15 | {
16 | // an array of cli handler options
17 | private $options;
18 |
19 | /**
20 | * Initializes the cli task from the given configuration.
21 | * @param array $options The task options.
22 | */
23 | public function init($options)
24 | {
25 | $this->setOptions($options);
26 | }
27 |
28 | /**
29 | * Returns the current set of options.
30 | * @return array The current set of options.
31 | */
32 | public function getOptions()
33 | {
34 | return $this->options;
35 | }
36 |
37 | /**
38 | * Sets the current set of options.
39 | * @param array $options The set of options.
40 | * @return AbstractTask Returns $this.
41 | */
42 | public function setOptions($options)
43 | {
44 | $this->options = $options;
45 | return $this;
46 | }
47 |
48 | /**
49 | * Retrieve an element from the DI container.
50 | *
51 | * @param string $key The DI key.
52 | * @param boolean $useCache (optional) An optional indicating whether we
53 | * should use the cached version of the element (true by default).
54 | * @return mixed Returns the DI element mapped to that key.
55 | * @throws Exception
56 | */
57 | public function get($key, $useCache = true)
58 | {
59 | return Di::getDefault()->get($key, $useCache);
60 | }
61 |
62 | /**
63 | * Sets an element in the DI container for the specified key.
64 | * @param string $key The DI key.
65 | * @param mixed $element The DI element to store.
66 | * @return Di Returns the Di instance.
67 | */
68 | public function set($key, $element)
69 | {
70 | return Di::getDefault()->set($key, $element);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Vectorface/SnappyRouter/Task/TaskInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface TaskInterface
11 | {
12 | /**
13 | * Initializes the cli task from the given configuration.
14 | * @param array $options The task options.
15 | */
16 | public function init($options);
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Authentication/CallbackAuthenticatorTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($auth->authenticate(['a', 'b']));
22 |
23 | $bool = false;
24 | $auth = new CallbackAuthenticator(function() use ($bool) {
25 | return $bool;
26 | });
27 | $this->assertFalse($auth->authenticate(['a', 'b']));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Config/ConfigTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class ConfigTest extends TestCase
15 | {
16 | /**
17 | * Demonstrates basic usage of the Config wrapper class.
18 | *
19 | * @throws Exception
20 | */
21 | public function testSynopsis()
22 | {
23 | $arrayConfig = [
24 | 'key1' => 'value1',
25 | 'key2' => 'value2'
26 | ];
27 |
28 | // initialize the class from an array
29 | $config = new Config($arrayConfig);
30 |
31 | // assert all the keys and values match
32 | foreach ($arrayConfig as $key => $value) {
33 | // using the array accessor syntax
34 | $this->assertEquals($value, $config[$key]);
35 | // using the get method
36 | $this->assertEquals($value, $config->get($key));
37 | }
38 |
39 | $config['key3'] = 'value3';
40 | $this->assertEquals('value3', $config['key3']);
41 |
42 | $config->set('key4', 'value4');
43 | $this->assertEquals('value4', $config['key4']);
44 |
45 | unset($config['key4']);
46 | $this->assertNull($config['key4']); // assert we unset the value
47 | $this->assertEquals(false, $config->get('key4', false)); // test default values
48 |
49 | unset($config['key3']);
50 | $this->assertEquals($arrayConfig, $config->toArray());
51 | }
52 |
53 | /**
54 | * Test that we cannot append to the config class like we would a normal array.
55 | */
56 | public function testExceptionThrownWhenConfigIsAppended()
57 | {
58 | $this->expectException(Exception::class);
59 | $this->expectExceptionMessage("Config values must contain a key.");
60 |
61 | $config = new Config([]);
62 | $config[] = 'new value';
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Controller/NonNamespacedController.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class TestDummyController extends AbstractController
14 | {
15 | public function indexAction()
16 | {
17 | }
18 |
19 | public function testAction()
20 | {
21 | return 'This is a test service.';
22 | }
23 |
24 | public function genericExceptionAction()
25 | {
26 | throw new InternalErrorException('A generic exception.');
27 | }
28 |
29 | public function defaultAction()
30 | {
31 | // ensure some abstract methods work
32 | $this->set('request', $this->getRequest());
33 | $this->get('request');
34 | }
35 |
36 | public function arrayAction()
37 | {
38 | $this->viewContext['variable'] = 'broken';
39 | return ['variable' => 'test'];
40 | }
41 |
42 | public function otherViewAction()
43 | {
44 | return $this->renderView(
45 | ['variable' => 'test'],
46 | 'test/array.twig'
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Controller/Views/test/array.twig:
--------------------------------------------------------------------------------
1 | This is a {{ variable }} service.
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Controller/Views/test/default.twig:
--------------------------------------------------------------------------------
1 | Hello world
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Di/DiTest.php:
--------------------------------------------------------------------------------
1 | set($key, $element);
24 | // check that we get back what we expect
25 | $this->assertEquals(
26 | $expected,
27 | $di->get($key, false)
28 | );
29 | // check we get the same value if we use the cache
30 | $this->assertEquals(
31 | $expected,
32 | $di->get($key, true)
33 | );
34 | // and again if we force a "no cache" hit
35 | $this->assertEquals(
36 | $expected,
37 | $di->get($key, false)
38 | );
39 | $this->assertTrue($di->hasElement($key));
40 | $this->assertEquals(
41 | [$key],
42 | $di->allRegisteredElements()
43 | );
44 | }
45 |
46 | /**
47 | * Data provider for the method testSetAndGetService.
48 | */
49 | public function setAndGetServiceProvider()
50 | {
51 | return [
52 | [
53 | 'HelloWorldService',
54 | 'Hello world!',
55 | 'Hello world!'
56 | ],
57 | [
58 | 'HelloWorldService',
59 | function() {
60 | return 'Hello world!';
61 | },
62 | 'Hello world!'
63 | ]
64 | ];
65 | }
66 |
67 | /**
68 | * Tests the methods for getting, setting and clearing the default
69 | * service provider.
70 | */
71 | public function testGetDefaultAndSetDefault()
72 | {
73 | Di::clearDefault(); // guard condition
74 | $di = Di::getDefault(); // get a fresh default
75 | $this->assertInstanceOf(Di::class, $di);
76 |
77 | Di::setDefault($di);
78 | $this->assertEquals($di, Di::getDefault());
79 | }
80 |
81 | /**
82 | * Tests the exception is thrown when we ask for a service that has not
83 | * been registered.
84 | */
85 | public function testMissingServiceThrowsException()
86 | {
87 | $this->expectException(Exception::class);
88 | $this->expectExceptionMessage("No element registered for key: TestElement");
89 |
90 | $di = new Di();
91 | $di->get('TestElement');
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Di/ServiceProviderTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ServiceProviderTest extends TestCase
16 | {
17 | /**
18 | * An overview of how to use the ServiceProvider class.
19 | *
20 | * @throws Exception
21 | */
22 | public function testSynopsis()
23 | {
24 | // instantiate the class
25 | $config = [
26 | 'TestController' => TestDummyController::class
27 | ];
28 | $serviceProvider = new ServiceProvider($config);
29 |
30 | // public setters (object chaining)
31 | $services = array_merge(
32 | $config,
33 | [
34 | 'AnotherService' => '/path/to/anotherService.php',
35 | 'AnotherServiceForFileClass' => null
36 | ]
37 | );
38 |
39 | $serviceProvider->setService('AnotherService', '/path/to/anotherService.php');
40 | $serviceProvider->setService(
41 | 'AnotherServiceForFileClass',
42 | [
43 | 'file' => 'tests/Vectorface/SnappyRouterTests/Controller/NonNamespacedController.php',
44 | 'class' => 'NonNamespacedController',
45 | ]
46 | );
47 |
48 | // public getters
49 | $this->assertEquals(
50 | array_keys($services),
51 | $serviceProvider->getServices()
52 | );
53 | $this->assertEquals(
54 | '/path/to/anotherService.php',
55 | $serviceProvider->getService('AnotherService')
56 | );
57 |
58 | $this->assertInstanceOf(
59 | TestDummyController::class,
60 | $serviceProvider->getServiceInstance('TestController')
61 | );
62 |
63 | //Tests instanceCache
64 | $this->assertInstanceOf(
65 | TestDummyController::class,
66 | $serviceProvider->getServiceInstance('TestController')
67 | );
68 |
69 | $this->assertInstanceOf(
70 | 'NonNamespacedController',
71 | $serviceProvider->getServiceInstance('AnotherServiceForFileClass')
72 | );
73 | }
74 |
75 | /**
76 | * Test that we can retrieve a non namespaced service.
77 | *
78 | * @throws Exception
79 | */
80 | public function testNonNamespacedService()
81 | {
82 | $config = [
83 | 'NonNamespacedController' => 'tests/Vectorface/SnappyRouterTests/Controller/NonNamespacedController.php'
84 | ];
85 | $serviceProvider = new ServiceProvider($config);
86 |
87 | $this->assertInstanceOf(
88 | 'NonNamespacedController',
89 | $serviceProvider->getServiceInstance('NonNamespacedController')
90 | );
91 | }
92 |
93 | /**
94 | * Test that we can retrieve a service while in namespace provisioning mode.
95 | *
96 | * @throws Exception
97 | */
98 | public function testNamespaceProvisioning()
99 | {
100 | $serviceProvider = new ServiceProvider([]);
101 | $namespaces = ['Vectorface\SnappyRouterTests\Controller'];
102 | $serviceProvider->setNamespaces($namespaces);
103 |
104 | $this->assertInstanceOf(
105 | TestDummyController::class,
106 | $serviceProvider->getServiceInstance('TestDummyController')
107 | );
108 | }
109 |
110 | /**
111 | * Test that we get an exception if we cannot find the service in any
112 | * of the given namespaces.
113 | *
114 | * @throws Exception
115 | */
116 | public function testNamespaceProvisioningMissingService()
117 | {
118 | $this->expectException(Exception::class);
119 | $this->expectExceptionMessage("Controller class TestDummyController was not found in any listed namespace.");
120 |
121 | $serviceProvider = new ServiceProvider([]);
122 | $serviceProvider->setNamespaces([]);
123 |
124 | $this->assertInstanceOf(
125 | TestDummyController::class,
126 | $serviceProvider->getServiceInstance('TestDummyController')
127 | );
128 | }
129 |
130 | /**
131 | * Test that we can retrieve a service while in folder provisioning mode.
132 | *
133 | * @throws Exception
134 | */
135 | public function testFolderProvisioning()
136 | {
137 | $serviceProvider = new ServiceProvider([]);
138 | $folders = [realpath(__DIR__.'/../')];
139 | $serviceProvider->setFolders($folders);
140 |
141 | $this->assertInstanceOf(
142 | 'NonNamespacedController',
143 | $serviceProvider->getServiceInstance('NonNamespacedController')
144 | );
145 | }
146 |
147 | /**
148 | * Test that we get an exception if we cannot find the service in any
149 | * of the given folders (recursively checking).
150 | *
151 | * @throws Exception
152 | */
153 | public function testFolderProvisioningMissingService()
154 | {
155 | $this->expectException(Exception::class);
156 | $this->expectExceptionMessage("Controller class NonExistentController not found in any listed folder.");
157 |
158 | $serviceProvider = new ServiceProvider([]);
159 | $folders = [realpath(__DIR__.'/../')];
160 | $serviceProvider->setFolders($folders);
161 |
162 | $serviceProvider->getServiceInstance('NonExistentController');
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Encoder/AbstractEncoderTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | abstract class AbstractEncoderTest extends TestCase
15 | {
16 | /**
17 | * Tests the encode method of the encoder.
18 | *
19 | * @dataProvider encodeProvider
20 | * @param string $expected
21 | * @param mixed $input
22 | */
23 | public function testEncode($expected, $input)
24 | {
25 | $encoder = $this->getEncoder();
26 | $this->assertEquals(
27 | $expected,
28 | $encoder->encode(
29 | new Response($input)
30 | )
31 | );
32 | }
33 |
34 | /**
35 | * Returns the encoder to be tested.
36 | * @return AbstractEncoder Returns an instance of an encoder.
37 | */
38 | abstract public function getEncoder();
39 |
40 | /**
41 | * A data provider for the testEncode method.
42 | */
43 | abstract public function encodeProvider();
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Encoder/JsonEncoderTest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class JsonEncoderTest extends AbstractEncoderTest
17 | {
18 | /**
19 | * Returns the encoder to be tested.
20 | * @return AbstractEncoder Returns an instance of an encoder.
21 | */
22 | public function getEncoder()
23 | {
24 | return new JsonEncoder();
25 | }
26 |
27 | /**
28 | * A data provider for the testEncode method.
29 | */
30 | public function encodeProvider()
31 | {
32 | $testObject = new stdClass();
33 | $testObject->id = 1234;
34 | return [
35 | [
36 | '"test1234"',
37 | 'test1234'
38 | ],
39 | [
40 | '{"id":1234}',
41 | ['id' => 1234]
42 | ],
43 | [
44 | '{"id":1234}',
45 | $testObject,
46 | ],
47 | [
48 | 'null',
49 | null
50 | ],
51 | [
52 | '"testSerialize"',
53 | $this
54 | ]
55 | ];
56 | }
57 |
58 | /**
59 | * Tests that we get an exception if we attempt to encode something that
60 | * is not serializable as JSON.
61 | *
62 | * @throws EncoderException
63 | */
64 | public function testNonSerializableEncode()
65 | {
66 | $this->expectException(EncoderException::class);
67 | $this->expectExceptionMessage("Unable to encode response as JSON.");
68 |
69 | $encoder = new JsonEncoder();
70 | $resource = fopen(__FILE__, 'r'); // resources can't be serialized
71 | $encoder->encode(new Response($resource));
72 | }
73 |
74 | public function jsonSerialize()
75 | {
76 | return 'testSerialize';
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Encoder/JsonpEncoderTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class JsonpEncoderTest extends AbstractEncoderTest
15 | {
16 | /**
17 | * Returns the encoder to be tested.
18 | *
19 | * @return AbstractEncoder Returns an instance of an encoder.
20 | * @throws Exception
21 | */
22 | public function getEncoder()
23 | {
24 | $options = [
25 | 'clientMethod' => 'doSomething'
26 | ];
27 | return new JsonpEncoder($options);
28 | }
29 |
30 | /**
31 | * A data provider for the testEncode method.
32 | */
33 | public function encodeProvider()
34 | {
35 | return [
36 | [
37 | 'doSomething("test1234");',
38 | 'test1234'
39 | ]
40 | ];
41 | }
42 |
43 | /**
44 | * Tests that an exception is thrown if the client method is missing from
45 | * the options.
46 | */
47 | public function testMissingClientMethodThrowsException()
48 | {
49 | $this->expectException(Exception::class);
50 | $this->expectExceptionMessage("Client method missing from plugin options.");
51 |
52 | new JsonpEncoder([]);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Encoder/NullEncoderTest.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class NullEncoderTest extends AbstractEncoderTest
14 | {
15 | /**
16 | * Returns the encoder to be tested.
17 | * @return AbstractEncoder Returns an instance of an encoder.
18 | */
19 | public function getEncoder()
20 | {
21 | return new NullEncoder();
22 | }
23 |
24 | /**
25 | * A data provider for the testEncode method.
26 | */
27 | public function encodeProvider()
28 | {
29 | return [
30 | [
31 | 'test1234',
32 | 'test1234'
33 | ],
34 | [
35 | '',
36 | null
37 | ]
38 | ];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Exception/InternalErrorExceptionTest.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class InternalErrorExceptionTest extends TestCase
14 | {
15 | /**
16 | * An overview of how to use the exception.
17 | */
18 | public function testSynopsis()
19 | {
20 | $message = 'hello world';
21 | $exception = new InternalErrorException($message);
22 |
23 | $this->assertEquals($message, $exception->getMessage());
24 | $this->assertEquals(500, $exception->getAssociatedStatusCode());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Exception/MethodNotAllowedExceptionTest.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class MethodNotAllowedExceptionTest extends TestCase
14 | {
15 | /**
16 | * An overview of how the class works.
17 | */
18 | public function testSynopsis()
19 | {
20 | $exception = new MethodNotAllowedException(
21 | 'Cannot use GET',
22 | ['POST']
23 | );
24 | $this->assertEquals(405, $exception->getAssociatedStatusCode());
25 | try {
26 | throw $exception;
27 | } catch (MethodNotAllowedException $e) {
28 | $this->assertEquals('Cannot use GET', $e->getMessage());
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Handler/DirectScriptHandlerTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class DirectScriptHandlerTest extends TestCase
15 | {
16 | /**
17 | * An overview of how to use the class.
18 | *
19 | * @throws PluginException
20 | */
21 | public function testSynopsis()
22 | {
23 | // the configuration maps a path like /cgi-bin to this folder
24 | $config = [
25 | DirectScriptHandler::KEY_PATH_MAP => [
26 | '/cgi-bin' => __DIR__
27 | ]
28 | ];
29 | $handler = new DirectScriptHandler($config);
30 | $path = '/cgi-bin/test_script.php';
31 | // the file itself exists so we should get back true
32 | $this->assertTrue(
33 | $handler->isAppropriate($path, [], [], 'GET')
34 | );
35 | // the test script simply has `echo "Hello world!"`
36 | $expected = 'Hello world!';
37 | $this->assertEquals($expected, $handler->performRoute());
38 |
39 | // the script is not found so the handler should not be marked as
40 | // appropriate
41 | $path = '/cgi-bin/script_not_found.php';
42 | $this->assertFalse(
43 | $handler->isAppropriate($path, [], [], 'GET')
44 | );
45 | }
46 |
47 | /**
48 | * Tests that the getRequest() method returns null.
49 | *
50 | * @throws PluginException
51 | */
52 | public function testGetRequest()
53 | {
54 | $config = [
55 | DirectScriptHandler::KEY_PATH_MAP => [
56 | '/cgi-bin' => __DIR__
57 | ]
58 | ];
59 | $handler = new DirectScriptHandler($config);
60 | $this->assertTrue($handler->isAppropriate('/cgi-bin/test_script.php', [], [], 'GET'));
61 | $this->assertNull($handler->getRequest());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Handler/PatternMatchHandlerTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class PatternMatchHandlerTest extends TestCase
15 | {
16 | /**
17 | * Demonstrates how to use the PatternMatchHandler class.
18 | *
19 | * @throws PluginException
20 | */
21 | public function testSynopsis()
22 | {
23 | $config = [
24 | 'routes' => [
25 | '/user/{name}/{id:[0-9]+}' => [
26 | 'get' => function($routeParams) {
27 | return print_r($routeParams, true);
28 | }
29 | ],
30 | '/anotherRoute' => function() {
31 | return false;
32 | }
33 | ]
34 | ];
35 | $handler = new PatternMatchHandler($config);
36 | $this->assertTrue($handler->isAppropriate('/user/asdf/1234', [], [], 'GET'));
37 | $expected = print_r(['name' => 'asdf', 'id' => 1234], true);
38 | $this->assertEquals($expected, $handler->performRoute());
39 |
40 | // not a matching pattern
41 | $this->assertFalse($handler->isAppropriate('/user/1234', [], [], 'GET'));
42 |
43 | // matching pattern but invalid HTTP verb
44 | $this->assertFalse($handler->isAppropriate('/user/asdf/1234', [], [], 'POST'));
45 | }
46 |
47 | /**
48 | * Tests that the cached route handler works as well.
49 | *
50 | * @throws PluginException
51 | */
52 | public function testCachedRouteHandler()
53 | {
54 | $cacheFile = __DIR__.'/routes.cache';
55 | if (file_exists($cacheFile)) {
56 | unlink($cacheFile);
57 | }
58 | $config = [
59 | 'routes' => [
60 | '/user/{name}/{id:[0-9]+}' => [
61 | 'get' => function($routeParams) {
62 | return print_r($routeParams, true);
63 | }
64 | ],
65 | '/anotherRoute' => function() {
66 | return false;
67 | }
68 | ],
69 | 'routeCache' => [
70 | 'cacheFile' => $cacheFile
71 | ]
72 | ];
73 |
74 | $handler = new PatternMatchHandler($config);
75 | $this->assertTrue($handler->isAppropriate('/user/asdf/1234', [], [], 'GET'));
76 | $this->assertNotEmpty(file_get_contents($cacheFile));
77 | unlink($cacheFile);
78 | }
79 |
80 | /**
81 | * Tests that the getRequest() method returns null.
82 | *
83 | * @throws PluginException
84 | */
85 | public function testGetRequest()
86 | {
87 | $config = [
88 | 'routes' => [
89 | '/testRoute' => function() {
90 | return false;
91 | }
92 | ]
93 | ];
94 | $handler = new PatternMatchHandler($config);
95 | $this->assertTrue($handler->isAppropriate('/testRoute', [], [], 'GET'));
96 | $this->assertNull($handler->getRequest());
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Handler/RestHandlerTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class RestHandlerTest extends TestCase
19 | {
20 | /**
21 | * An overview of how to use the RestHandler class.
22 | *
23 | * @throws InternalErrorException|PluginException|ResourceNotFoundException
24 | */
25 | public function testSynopsis()
26 | {
27 | $options = [
28 | RestHandler::KEY_BASE_PATH => '/',
29 | Config::KEY_CONTROLLERS => [
30 | 'TestController' => TestDummyController::class
31 | ]
32 | ];
33 | $handler = new RestHandler($options);
34 | $this->assertTrue($handler->isAppropriate('/v1/test', [], [], 'GET'));
35 | $result = json_decode($handler->performRoute());
36 | $this->assertTrue(empty($result));
37 | }
38 |
39 | /**
40 | * Tests the possible paths that could be handled by the RestHandler.
41 | *
42 | * @dataProvider restPathsProvider
43 | * @param bool $expected
44 | * @param string $path
45 | * @throws InternalErrorException
46 | * @throws PluginException
47 | */
48 | public function testRestHandlerHandlesPath($expected, $path)
49 | {
50 | $options = [
51 | RestHandler::KEY_BASE_PATH => '/',
52 | Config::KEY_CONTROLLERS => [
53 | 'TestController' => TestDummyController::class,
54 | ]
55 | ];
56 | $handler = new RestHandler($options);
57 | $this->assertEquals($expected, $handler->isAppropriate($path, [], [], 'GET'));
58 | }
59 |
60 | /**
61 | * The data provider for testing various paths against the RestHandler.
62 | */
63 | public function restPathsProvider()
64 | {
65 | return [
66 | [
67 | true,
68 | '/v1/test'
69 | ],
70 | [
71 | true,
72 | '/v1.2/test'
73 | ],
74 | [
75 | true,
76 | '/v1.2/Test'
77 | ],
78 | [
79 | true,
80 | '/v1.2/test/1234'
81 | ],
82 | [
83 | true,
84 | '/v1.2/test/someAction'
85 | ],
86 | [
87 | true,
88 | '/v1.2/test/1234/someAction'
89 | ],
90 | [
91 | false,
92 | '/v1.2'
93 | ],
94 | [
95 | true,
96 | '/v1.2/noController'
97 | ],
98 | [
99 | false,
100 | '/v1.2/1234/5678'
101 | ]
102 | ];
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Handler/test_script.php:
--------------------------------------------------------------------------------
1 |
19 | * @author Dan Bruce
20 | */
21 | class AbstractAuthenticationPluginTest extends TestCase
22 | {
23 | /**
24 | * Authentication of service requests happens by intercepting preInvoke; Validate that.
25 | *
26 | * @throws InternalErrorException|UnauthorizedException|PluginException
27 | */
28 | public function testAfterHandlerInvoked()
29 | {
30 | $ignored = new ControllerHandler([]);
31 |
32 | /* Configure DI */
33 | $bool = false;
34 | $auth = new CallbackAuthenticator(function() use (&$bool) {
35 | return $bool;
36 | });
37 | $di = new Di(['AuthMechanism' => false]);
38 | Di::setDefault($di);
39 |
40 | /* Direct testing. */
41 | $plugin = new TestAuthenticationPlugin([]);
42 |
43 | try {
44 | $plugin->afterHandlerSelected($ignored);
45 | $this->fail("An invalid authenticator should yield an internal error");
46 | } catch (InternalErrorException $e) {
47 | $this->assertEquals(500, $e->getAssociatedStatusCode()); /* HTTP 500 ISE */
48 | }
49 |
50 | /* From here on out, use the "Do whatever I say" authenticator. :) */
51 | $di->set('AuthMechanism', $auth);
52 |
53 | $plugin->credentials = false;
54 | try {
55 | $plugin->afterHandlerSelected($ignored);
56 | $this->fail("No username and password are available. UnauthorizedException expected.");
57 | } catch (UnauthorizedException $e) {
58 | $this->assertEquals(401, $e->getAssociatedStatusCode()); /* HTTP 401 Unauthorized */
59 | }
60 |
61 | $plugin->credentials = ['ignored', 'ignored'];
62 | try {
63 | $plugin->afterHandlerSelected($ignored);
64 | $this->fail("Callback expected to return false auth result. UnauthorizedException expected.");
65 | } catch (UnauthorizedException $e) {
66 | // we expect the exception to be thrown
67 | }
68 |
69 | /* With a true result, preInvoke should pass through. */
70 | $bool = true;
71 | $this->assertTrue($bool);
72 | $plugin->afterHandlerSelected($ignored);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Plugin/Authentication/HttpBasicAuthenticationPluginTest.php:
--------------------------------------------------------------------------------
1 |
18 | * @author Dan Bruce
19 | */
20 | class HttpBasicAuthenticationPluginTest extends TestCase
21 | {
22 | /**
23 | * Test the HTTPBasicAuthenticationPlugin; All in one test!
24 | *
25 | * @throws InternalErrorException|UnauthorizedException|PluginException
26 | */
27 | public function testBasicHTTPAuth()
28 | {
29 | $ignored = new ControllerHandler([]);
30 |
31 | /* Configure DI */
32 | $di = new Di(['MyCustomAuth' => false]);
33 | Di::setDefault($di);
34 |
35 | /* Direct testing. */
36 | $plugin = new HttpBasicAuthenticationPlugin([
37 | 'AuthMechanism' => 'MyCustomAuth',
38 | 'realm' => 'Authentication Test'
39 | ]);
40 |
41 | try {
42 | $plugin->afterHandlerSelected($ignored);
43 | $this->fail("An invalid authenticator should yield an internal error");
44 | } catch (InternalErrorException $e) {
45 | $this->assertEquals(500, $e->getAssociatedStatusCode()); /* HTTP 500 ISE */
46 | }
47 |
48 | /* From here on out, use the "Do whatever I say" authenticator. :) */
49 | $bool = false;
50 | $auth = new CallbackAuthenticator(function() use (&$bool) {
51 | return $bool;
52 | });
53 | $di->set('MyCustomAuth', $auth);
54 |
55 |
56 | $_SERVER['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_PW'] = null;
57 | try {
58 | $plugin->afterHandlerSelected($ignored);
59 | $this->fail("No username and password are available. UnauthorizedException expected.");
60 | } catch (UnauthorizedException $e) {
61 | $this->assertEquals(401, $e->getAssociatedStatusCode()); /* HTTP 401 Unauthorized */
62 | }
63 |
64 | $_SERVER['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_PW'] = 'ignored';
65 | try {
66 | $plugin->afterHandlerSelected($ignored);
67 | $this->fail("Callback expected to return false auth result. UnauthorizedException expected.");
68 | } catch (UnauthorizedException $e) {
69 | // we expect the exception to be thrown
70 | }
71 |
72 | /* With a true result, preInvoke should pass through. */
73 | $bool = true;
74 | $this->assertTrue($bool);
75 | $plugin->afterHandlerSelected($ignored);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Plugin/Authentication/TestAuthenticationPlugin.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class TestAuthenticationPlugin extends AbstractAuthenticationPlugin
15 | {
16 | /**
17 | * The "credentials" returned by getCredentials for testing.
18 | *
19 | * @var mixed
20 | */
21 | public $credentials = ['ignored', 'ignored'];
22 |
23 | /**
24 | * Extract credentials from the "request"... Or the hard-coded test values above.
25 | *
26 | * @return mixed An array of credentials; A username and password pair, or false if credentials aren't available
27 | * @throws Exception
28 | */
29 | public function getCredentials()
30 | {
31 | return $this->set('credentials', $this->credentials)->get('credentials');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Plugin/HttpHeader/RouterHeaderPluginTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class RouterHeaderPluginTest extends TestCase
16 | {
17 | /**
18 | * An overview of how to use the plugin.
19 | *
20 | * @throws PluginException
21 | */
22 | public function testSynopsis()
23 | {
24 | $handler = new ControllerHandler([]);
25 | $plugin = new RouterHeaderPlugin([]);
26 | $plugin->afterHandlerSelected($handler);
27 | $this->assertEquals(1000, $plugin->getExecutionOrder());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Plugin/TestPlugin.php:
--------------------------------------------------------------------------------
1 | assertEquals(
19 | AbstractPlugin::PRIORITY_DEFAULT,
20 | $plugin->getExecutionOrder()
21 | );
22 |
23 | $plugin->setWhitelist(
24 | [
25 | 'TestController' => AbstractPlugin::ALL_ACTIONS
26 | ]
27 | );
28 | $this->assertTrue(
29 | $plugin->supportsControllerAndAction(
30 | 'TestController',
31 | 'someAction'
32 | )
33 | );
34 | }
35 |
36 | /**
37 | * Tests the supportsControllerAndAction methods.
38 | */
39 | public function testSupportsControllerAndAction()
40 | {
41 | $plugin = new TestPlugin([]);
42 |
43 | // no lists yet, so plugin supports everything
44 | $this->assertTrue(
45 | $plugin->supportsControllerAndAction(
46 | 'TestController',
47 | 'anyAction'
48 | )
49 | );
50 |
51 | // set a whitelist
52 | $plugin->setWhitelist(
53 | [
54 | 'TestController' => AbstractPlugin::ALL_ACTIONS,
55 | 'AnotherController' => [
56 | 'specificAction'
57 | ]
58 | ]
59 | );
60 | // all actions enabled for this controller
61 | $this->assertTrue(
62 | $plugin->supportsControllerAndAction(
63 | 'TestController',
64 | 'anyAction'
65 | )
66 | );
67 | // specific action enabled
68 | $this->assertTrue(
69 | $plugin->supportsControllerAndAction(
70 | 'AnotherController',
71 | 'specificAction'
72 | )
73 | );
74 | // controller is missing from whitelist
75 | $this->assertFalse(
76 | $plugin->supportsControllerAndAction(
77 | 'MissingController',
78 | 'anyAction'
79 | )
80 | );
81 | // action is missing from whitelist
82 | $this->assertFalse(
83 | $plugin->supportsControllerAndAction(
84 | 'AnotherController',
85 | 'differentAction'
86 | )
87 | );
88 |
89 | // now the reverse logic for the blacklist
90 | $plugin->setBlacklist(
91 | [
92 | 'TestController' => [
93 | 'bannedAction'
94 | ],
95 | 'BannedController' => AbstractPlugin::ALL_ACTIONS
96 | ]
97 | );
98 | // controller is missing from blacklist
99 | $this->assertTrue(
100 | $plugin->supportsControllerAndAction(
101 | 'MissingController',
102 | 'anyAction'
103 | )
104 | );
105 | // action is blacklisted specifically
106 | $this->assertFalse(
107 | $plugin->supportsControllerAndAction(
108 | 'TestController',
109 | 'bannedAction'
110 | )
111 | );
112 | // all actions for the controller are banned
113 | $this->assertFalse(
114 | $plugin->supportsControllerAndAction(
115 | 'BannedController',
116 | 'anyAction'
117 | )
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Request/HttpRequestTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class HttpRequestTest extends TestCase
15 | {
16 | /**
17 | * An overview of how to use the RPCRequest class.
18 | */
19 | public function testSynopsis()
20 | {
21 | // instantiate the class
22 | $request = new HttpRequest('MyService', 'MyMethod', 'GET', 'php://memory');
23 |
24 | $this->assertEquals('GET', $request->getVerb());
25 | $this->assertEquals('POST', $request->setVerb('POST')->getVerb());
26 |
27 | $queryData = ['id' => '1234'];
28 | $this->assertSame(
29 | 1234,
30 | $request->setQuery($queryData)->getQuery('id', 0, 'int')
31 | );
32 |
33 | $postData = ['username' => ' TEST_USER '];
34 | $this->assertEquals(
35 | 'test_user',
36 | $request->setPost($postData)->getPost('username', '', ['trim', 'lower'])
37 | );
38 |
39 | $this->assertEquals(
40 | ['id' => '1234', 'username' => ' TEST_USER '],
41 | $request->getAllInput()
42 | );
43 | }
44 |
45 | /**
46 | * Tests successful input stream set and fetch cases
47 | *
48 | * @throws InternalErrorException
49 | */
50 | public function testInputStream()
51 | {
52 | $tempStream = fopen('php://memory', 'w');
53 | fwrite($tempStream, "test");
54 | rewind($tempStream);
55 |
56 | /* Mock a stream in memory */
57 | $request = new HttpRequest('TestService', 'TestMethod', 'GET', $tempStream);
58 | $this->assertEquals("test", $request->getBody());
59 | fclose($tempStream);
60 |
61 | /* Fetch previously stored value */
62 | $this->assertEquals("test", $request->getBody());
63 |
64 | /* Specify php://memory as a string */
65 | $request = new HttpRequest('TestService', 'TestMethod', 'GET', 'php://memory');
66 | $this->assertEquals("", $request->getBody());
67 | }
68 |
69 | /**
70 | * Tests the input stream functionality where the stream source is not a string or a stream
71 | *
72 | * @throws InternalErrorException
73 | */
74 | public function testInputStreamIncorrectTypeFailure()
75 | {
76 | $this->expectException(InternalErrorException::class);
77 |
78 | $request = new HttpRequest('TestService', 'TestMethod', 'GET', 1);
79 | $request->getBody();
80 | }
81 |
82 | /**
83 | * Tests the input stream functionality where the stream source does not exist
84 | *
85 | * @throws InternalErrorException
86 | */
87 | public function testInputStreamIncorrectFileFailure()
88 | {
89 | $this->expectException(InternalErrorException::class);
90 |
91 | $request = new HttpRequest('TestService', 'TestMethod', 'GET', 'file');
92 | $request->getBody();
93 | }
94 |
95 | /**
96 | * Tests the various filters.
97 | *
98 | * @dataProvider filtersProvider
99 | * @param string $expected
100 | * @param string $value
101 | * @param string $filters
102 | */
103 | public function testInputFilters($expected, $value, $filters)
104 | {
105 | $request = new HttpRequest('TestService', 'TestMethod', 'GET', 'php://input');
106 | $request->setQuery(['key' => $value]);
107 | $this->assertSame($expected, $request->getQuery('key', null, $filters));
108 | }
109 |
110 | /**
111 | * The data provider for testInputFilters.
112 | */
113 | public function filtersProvider()
114 | {
115 | return [
116 | [
117 | 1234,
118 | '1234',
119 | 'int'
120 | ],
121 | [
122 | 1234.5,
123 | ' 1234.5 ',
124 | 'float'
125 | ],
126 | [
127 | 'hello world',
128 | "\t".'hello world '.PHP_EOL,
129 | 'trim'
130 | ],
131 | [
132 | 'hello world',
133 | 'HELLO WORLD',
134 | 'lower'
135 | ],
136 | [
137 | 'HELLO WORLD',
138 | 'hello world',
139 | 'upper'
140 | ],
141 | [
142 | 'test'.PHP_EOL.'string',
143 | 'test'.PHP_EOL.' '.PHP_EOL.PHP_EOL.'string',
144 | 'squeeze'
145 | ],
146 | ];
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Request/JsonRpcRequestTest.php:
--------------------------------------------------------------------------------
1 | 'remoteProcedure',
23 | 'params' => [1, 2, 3],
24 | 'id' => 'uniqueidentifier'
25 | ]);
26 |
27 | $this->assertEquals('POST', $request->getVerb());
28 | $this->assertEquals('remoteProcedure', $request->getMethod());
29 | $this->assertEquals('1.0', $request->getVersion());
30 | $this->assertEquals([1, 2, 3], $request->getParameters());
31 | $this->assertEquals('uniqueidentifier', $request->getIdentifier());
32 | $this->assertNull($request->getPost('anything')); // Post should be ignored.
33 |
34 | /* Handles JSON-RPC 2.0 requests. */
35 | $request = new JsonRpcRequest('MyService', (object)[
36 | 'jsonrpc' => '2.0',
37 | 'method' => 'remoteProcedure',
38 | 'id' => 'uniqueidentifier'
39 | ]);
40 |
41 | $this->assertEquals('2.0', $request->getVersion());
42 | $this->assertEquals([], $request->getParameters());
43 |
44 | /* Catches invalid request format. */
45 | $request = new JsonRpcRequest('MyService', (object)[
46 | 'jsonrpc' => '2.0',
47 | 'method' => null
48 | ]);
49 | $this->assertFalse($request->isValid());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Response/JsonRpcResponseTest.php:
--------------------------------------------------------------------------------
1 | '2.0',
25 | 'method' => 'remoteProcedure',
26 | 'params' => [1, 2, 3],
27 | 'id' => 'identifier'
28 | ]);
29 |
30 | $response = new JsonRpcResponse('object, array, or scalar', null, $request);
31 | $obj = $response->getResponseObject();
32 | $this->assertEquals("2.0", $obj->jsonrpc, "Responds with the same version");
33 | $this->assertEquals('object, array, or scalar', $obj->result, "Result is passed through");
34 | $this->assertEquals("identifier", $obj->id, "Request ID is passed back");
35 |
36 | /* Notifications generate no response */
37 | $request = new JsonRpcRequest('MyService', (object)['method' => 'notifyProcedure']);
38 | $response = new JsonRpcResponse("anything", null, $request);
39 | $this->assertEquals("", $response->getResponseObject());
40 |
41 | /* An error passes back a message and a code */
42 | $request = new JsonRpcRequest('MyService', (object)['method' => 'any', 'id' => 123]);
43 | $response = new JsonRpcResponse(null, new Exception("ex", 123), $request);
44 | $obj = $response->getResponseObject();
45 | $this->assertEquals(123, $obj->error->code);
46 | $this->assertEquals("ex", $obj->error->message);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/SnappyRouterTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class SnappyRouterTest extends TestCase
19 | {
20 | /**
21 | * An overview of how to use the SnappyRouter class.
22 | *
23 | * @throws Exception
24 | */
25 | public function testSynopsis()
26 | {
27 | // an example configuration of the router
28 | $config = $this->getStandardConfig();
29 | // instantiate the router
30 | $router = new SnappyRouter(new Config($config));
31 | // configure a logger, if insight into router behavior is desired
32 | $router->setLogger(new NullLogger());
33 |
34 | // an example MVC request
35 | $_SERVER['REQUEST_URI'] = '/Test/test';
36 | $_SERVER['REQUEST_METHOD'] = 'GET';
37 | $_GET['param'] = 'value';
38 | $response = $router->handleRoute('apache2handler');
39 |
40 | $expectedResponse = 'This is a test service.';
41 | $this->assertEquals($expectedResponse, $response);
42 |
43 | unset($_SERVER['REQUEST_URI']);
44 | $_GET = [];
45 | $_POST = [];
46 | }
47 |
48 | /**
49 | * Returns a standard router config array.
50 | * @return array A standard router config.
51 | */
52 | private function getStandardConfig()
53 | {
54 | return [
55 | Config::KEY_DI => 'Vectorface\SnappyRouter\Di\Di',
56 | Config::KEY_HANDLERS => [
57 | 'BogusCliHandler' => [
58 | Config::KEY_CLASS => 'Vectorface\SnappyRouter\Handler\CliTaskHandler'
59 | ],
60 | 'ControllerHandler' => [
61 | Config::KEY_CLASS => 'Vectorface\SnappyRouter\Handler\ControllerHandler',
62 | Config::KEY_OPTIONS => [
63 | ControllerHandler::KEY_BASE_PATH => '/',
64 | Config::KEY_CONTROLLERS => [
65 | 'TestController' => 'Vectorface\SnappyRouterTests\Controller\TestDummyController'
66 | ],
67 | Config::KEY_PLUGINS => [
68 | 'TestPlugin' => [
69 | Config::KEY_CLASS => 'Vectorface\SnappyRouterTests\Plugin\TestPlugin',
70 | Config::KEY_OPTIONS => []
71 | ],
72 | 'AnotherPlugin' => 'Vectorface\SnappyRouterTests\Plugin\TestPlugin'
73 | ]
74 | ]
75 | ],
76 | 'CliHandler' => [
77 | Config::KEY_CLASS => 'Vectorface\SnappyRouter\Handler\CliTaskHandler',
78 | Config::KEY_OPTIONS => [
79 | 'tasks' => [
80 | 'TestTask' => 'Vectorface\SnappyRouterTests\Task\DummyTestTask'
81 | ]
82 | ]
83 | ]
84 | ]
85 | ];
86 | }
87 |
88 | /**
89 | * Tests that the router handles a generic exception.
90 | *
91 | * @throws Exception
92 | */
93 | public function testGenericException()
94 | {
95 | $config = $this->getStandardConfig();
96 | $router = new SnappyRouter(new Config($config));
97 |
98 | // an example MVC request
99 | $path = '/Test/genericException';
100 | $query = ['jsoncall' => 'testMethod'];
101 | $response = $router->handleHttpRoute($path, $query, [], 'get');
102 |
103 | $expectedResponse = 'A generic exception.';
104 | $this->assertEquals($expectedResponse, $response);
105 | }
106 |
107 | /**
108 | * Tests that an empty config array results in no handler being found.
109 | *
110 | * @throws Exception
111 | */
112 | public function testNoHandlerFoundException()
113 | {
114 | // turn on debug mode so we get a verbose description of the exception
115 | $router = new SnappyRouter(new Config([
116 | 'debug' => true
117 | ]));
118 |
119 | // an example MVC request
120 | $path = '/Test/test';
121 | $query = ['jsoncall' => 'testMethod'];
122 | $response = $router->handleHttpRoute($path, $query, [], 'get');
123 | $this->assertEquals('No handler responded to the request.', $response);
124 | }
125 |
126 | /**
127 | * Tests that an exception is thrown if a handler class does not exist.
128 | *
129 | * @throws Exception
130 | */
131 | public function testInvalidHandlerClass()
132 | {
133 | $this->expectException(Exception::class);
134 | $this->expectExceptionMessage("Cannot instantiate instance of Vectorface\SnappyRouter\Handler\NonexistentHandler");
135 |
136 | $config = $this->getStandardConfig();
137 | $config[Config::KEY_HANDLERS]['InvalidHandler'] = [
138 | 'class' => 'Vectorface\SnappyRouter\Handler\NonexistentHandler'
139 | ];
140 | $router = new SnappyRouter(new Config($config));
141 |
142 | // an example MVC request
143 | $path = '/Test/test';
144 | $query = ['jsoncall' => 'testMethod'];
145 | $response = $router->handleHttpRoute($path, $query, [], 'get');
146 |
147 | $expectedResponse = 'No handler responded to request.';
148 | $this->assertEquals($expectedResponse, $response);
149 | }
150 |
151 | /**
152 | * Tests that the CLI routing functionality works.
153 | *
154 | * @throws Exception
155 | */
156 | public function testStandardCliRoute()
157 | {
158 | $config = $this->getStandardConfig();
159 | $router = new SnappyRouter(new Config($config));
160 |
161 | $_SERVER['argv'] = [
162 | 'dummyScript.php',
163 | '--task',
164 | 'TestTask',
165 | '--action',
166 | 'testMethod'
167 | ];
168 | $_SERVER['argc'] = count($_SERVER['argv']);
169 | $response = $router->handleRoute();
170 |
171 | $expected = 'Hello World'.PHP_EOL;
172 | $this->assertEquals($expected, $response);
173 | }
174 |
175 | /**
176 | * Tests a CLI route that throws an exception.
177 | *
178 | * @throws Exception
179 | */
180 | public function testCliRouteWithException()
181 | {
182 | $config = $this->getStandardConfig();
183 | $router = new SnappyRouter(new Config($config));
184 |
185 | $_SERVER['argv'] = [
186 | 'dummyScript.php',
187 | '--task',
188 | 'TestTask',
189 | '--action',
190 | 'throwsException'
191 | ];
192 | $_SERVER['argc'] = count($_SERVER['argv']);
193 | $response = $router->handleRoute();
194 |
195 | $expected = 'An exception was thrown.'.PHP_EOL;
196 | $this->assertEquals($expected, $response);
197 | }
198 |
199 | /**
200 | * Tests that a CLI route with no appropriate handlers throws an
201 | * exception.
202 | *
203 | * @throws Exception
204 | */
205 | public function testCliRouteWithNoHandler()
206 | {
207 | $config = $this->getStandardConfig();
208 | $router = new SnappyRouter(new Config($config));
209 |
210 | $_SERVER['argv'] = [
211 | 'dummyScript.php',
212 | '--task',
213 | 'NotDefinedTask',
214 | '--action',
215 | 'anyAction'
216 | ];
217 | $_SERVER['argc'] = count($_SERVER['argv']);
218 | $response = $router->handleRoute();
219 |
220 | $expected = 'No CLI handler registered.'.PHP_EOL;
221 | $this->assertEquals($expected, $response);
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Task/CliTaskHandlerTest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class CliTaskHandlerTest extends TestCase
17 | {
18 | /**
19 | * An overview of how to use the CliTaskHandler class.
20 | *
21 | * @throws PluginException
22 | */
23 | public function testSynopsis()
24 | {
25 | $options = [
26 | Config::KEY_TASKS => [
27 | 'TestTask' => DummyTestTask::class,
28 | ]
29 | ];
30 | $handler = new CliTaskHandler($options);
31 | $components = [
32 | 'dummyScript.php',
33 | '--task',
34 | 'TestTask',
35 | '--action',
36 | 'testAction'
37 | ];
38 |
39 | // the components needs to be at least 5 elements with --task and --action
40 | $this->assertTrue($handler->isAppropriate($components));
41 |
42 | // assert the handler is not appropriate if we only have 4 elements
43 | $this->assertFalse($handler->isAppropriate(array_slice($components, 0, 4)));
44 |
45 | // assert the handler is not appropriate if --task and --action are missing
46 | $badComponents = $components;
47 | $badComponents[1] = '--service';
48 | $this->assertFalse($handler->isAppropriate($badComponents));
49 | }
50 |
51 | /**
52 | * A test that asserts an exception is thrown if we call an action missing
53 | * from a registered task.
54 | *
55 | * @throws PluginException|ResourceNotFoundException
56 | */
57 | public function testMissingActionOnTask()
58 | {
59 | $this->expectException(ResourceNotFoundException::class);
60 | $this->expectExceptionMessage("TestTask task does not have action missingAction.");
61 |
62 | $options = [
63 | Config::KEY_TASKS => [
64 | 'TestTask' => DummyTestTask::class,
65 | ]
66 | ];
67 | $handler = new CliTaskHandler($options);
68 | $components = [
69 | 'dummyScript.php',
70 | '--task',
71 | 'TestTask',
72 | '--action',
73 | 'missingAction'
74 | ];
75 | $this->assertTrue($handler->isAppropriate($components));
76 | $handler->performRoute();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Vectorface/SnappyRouterTests/Task/DummyTestTask.php:
--------------------------------------------------------------------------------
1 | getOptions();
17 | $this->set('taskOptions', $options);
18 | $this->set('response', 'Hello World');
19 | return $this->get('response');
20 | }
21 |
22 | /**
23 | * @throws Exception
24 | */
25 | public function throwsException()
26 | {
27 | throw new Exception('An exception was thrown.');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |