|
| 1 | +# WP CoderPress Endpoints |
| 2 | + |
| 3 | +This package intends to provide a more straightforward way to add custom endpoints to Wordpress Rest API (added to mu-plugins with no hooks, as a suggestion), also using a Facade pattern which allows: |
| 4 | +* To attach PSR-16 compliant cache drivers of any nature |
| 5 | +* To attach as many middlewares as you want to modify the request or provide a particular response |
| 6 | +* Interfere in the way cached responses are stored and retrieved (JSON, serialize(), igbinary_serialize()) |
| 7 | +* Provide a port to add multiple custom endpoints without repeating code and sharing cache, middleware and variables within, including a dynamic endpoint generation |
| 8 | +* Offer common middlewares to handle common scenarios in REST API development |
| 9 | + |
| 10 | +# Basic Example |
| 11 | + |
| 12 | +The following example creates a very simple example endpoint to the rest API that comes with two middlewares attached (as \Closure objects). The first one modifies any passed parameter to "id = 123", which triggers the second middleware and returns a WP_REST_Response - this also prevents the cache to be done. |
| 13 | + |
| 14 | +If the second middleware is removed, the endpoint is no longer short-circuited, so the results are now serialized and cached in a file within wp-content/cache/. In the example, the cache is managed by FileCache, but this package also includes usable drivers for Redis (using redis-php extension) and MySQL cache, using a custom DB table. |
| 15 | + |
| 16 | +```php |
| 17 | +use CoderPress\Rest\{RestEndpointFacade, AbstractRestEndpoint}; |
| 18 | +use CoderPress\Cache\{RedisCache, MySqlCache, FileCache}; |
| 19 | + |
| 20 | +require __DIR__ . '/vendor/autoload.php'; |
| 21 | + |
| 22 | +$cacheInstance = new FileCache(WP_CONTENT_DIR . '/cache/'); |
| 23 | + |
| 24 | +$custom = RestEndpointFacade::createEndpoint( |
| 25 | + /** The five first arguments stand as they are for register_rest_route() |
| 26 | + * but with callbacks now accepting \Closure functions instead. |
| 27 | + * |
| 28 | + * The middlewares arguments accepts an array of closures which will be |
| 29 | + * sequentially executed and MUST have a $request parameter. |
| 30 | + * |
| 31 | + * The cache mechanism is automatically applied to the endpoint and accepts |
| 32 | + * any PSR-16 compliant object. Optionally, the expire time and the type |
| 33 | + * of serialization can be changed. Expires accepts any value in seconds as |
| 34 | + * integer or a Datetime object (and then the time in seconds between that and |
| 35 | + * the current time will be automatically extracted) |
| 36 | + * |
| 37 | + */ |
| 38 | + namespace: 'testing/v1', |
| 39 | + route: 'custom/', |
| 40 | + args: [ |
| 41 | + 'id' => [ |
| 42 | + 'validate_callback' => function($param, $request, $key) { |
| 43 | + return is_numeric($param); |
| 44 | + } |
| 45 | + ], |
| 46 | + ], |
| 47 | + callback: function (\WP_REST_Request $request) { |
| 48 | + $postId = $request->get_param('id'); |
| 49 | + $postCategoryIds = wp_get_object_terms($postId, ['category'], ['fields' => 'ids']); |
| 50 | + |
| 51 | + return get_posts([ |
| 52 | + 'post_status' => 'publish', |
| 53 | + 'category__in' => $postCategoryIds, |
| 54 | + 'order' => 'relevance' |
| 55 | + ]); |
| 56 | + }, |
| 57 | + permissionCallback: function () { |
| 58 | + return is_user_logged_in(); |
| 59 | + }, |
| 60 | + /** |
| 61 | + * Accepts any number of /Closure middleware functions - to each one of them, |
| 62 | + * the $request object must be passed. For changes made to the WP_REST_Request object, |
| 63 | + * the closure must return void(). However, the middleware can also return a response |
| 64 | + * to the request easily by return a new WP_REST_Response object instead. |
| 65 | + */ |
| 66 | + middlewares: [ |
| 67 | + function (\WP_REST_Request $request) { |
| 68 | + $request->set_param('id', 123); |
| 69 | + }, |
| 70 | + function (\WP_REST_Request $request) { |
| 71 | + if ($request->get_param('id') == 123) { |
| 72 | + return new \WP_REST_Response(['message' => 'Invalid value'], 201); |
| 73 | + } |
| 74 | + } |
| 75 | + ], |
| 76 | + /** |
| 77 | + * Accepts any instance PSR-16 compliant (CacheInterface contract), |
| 78 | + * this package comes with 3 usable examples (file, Redis and custom MySQL |
| 79 | + * table caching drivers). |
| 80 | + */ |
| 81 | + cache: $cacheInstance, |
| 82 | + /** |
| 83 | + * Accepts 3 different serialize/unserialize methods - defaults to serialize(), |
| 84 | + * but can be using JSON - AbstractRestEndpoint::SERIALIZE_JSON - or igbinary PHP |
| 85 | + * extension which offers a more efficient serializing version |
| 86 | + * - AbstractRestEndpoint::SERIALIZE_IGBINARY. |
| 87 | + */ |
| 88 | + cacheSerializingMethod: AbstractRestEndpoint::SERIALIZE_PHP, |
| 89 | + /** |
| 90 | + * Accepts seconds as integer value, but also DateTime objects - for the latter, |
| 91 | + * the system will get the interval between NOW and the passed DateTime object time, |
| 92 | + * so the cache will work as scheduled. |
| 93 | + */ |
| 94 | + cacheExpires: (new \DateTime())->modify('+1 day') |
| 95 | +); |
| 96 | +``` |
| 97 | + |
| 98 | +# Middlewares |
| 99 | + |
| 100 | +The package includes several built-in middlewares to handle common scenarios in REST API development: |
| 101 | + |
| 102 | +## CORS Middleware |
| 103 | + |
| 104 | +The CORS (Cross-Origin Resource Sharing) middleware allows you to configure cross-origin requests to your API endpoints. Here's how to use it: |
| 105 | + |
| 106 | +```php |
| 107 | +use CoderPress\Rest\RestEndpointFacade; |
| 108 | +use CoderPress\Rest\Middleware\MiddlewareFactory; |
| 109 | + |
| 110 | +// Create an endpoint with CORS support |
| 111 | +RestEndpointFacade::createEndpoint( |
| 112 | + namespace: 'api/v1', |
| 113 | + route: 'posts', |
| 114 | + callback: function($request) { |
| 115 | + return ['data' => 'Your API response']; |
| 116 | + }, |
| 117 | + permissionCallback: function() { |
| 118 | + return true; |
| 119 | + }, |
| 120 | + args: [], |
| 121 | + methods: ['GET', 'POST', 'OPTIONS'], |
| 122 | + middlewares: [ |
| 123 | + MiddlewareFactory::cors( |
| 124 | + allowedOrigins: ['https://your-domain.com'], |
| 125 | + allowedMethods: ['GET', 'POST'], |
| 126 | + allowedHeaders: ['Content-Type', 'X-Custom-Header'], |
| 127 | + maxAge: 7200 |
| 128 | + ) |
| 129 | + ] |
| 130 | +); |
| 131 | +``` |
| 132 | + |
| 133 | +The CORS middleware provides: |
| 134 | +- Origin validation against a whitelist |
| 135 | +- Configurable HTTP methods |
| 136 | +- Customizable allowed headers |
| 137 | +- Preflight request handling |
| 138 | +- Configurable cache duration for preflight responses |
| 139 | +- Credentials support |
| 140 | + |
| 141 | +All parameters are optional and come with sensible defaults for typical API usage. |
| 142 | + |
| 143 | +## Sanitization Middleware |
| 144 | + |
| 145 | +The Sanitization middleware provides comprehensive input sanitization and validation for your API endpoints. It helps prevent XSS attacks, SQL injection, and ensures data consistency: |
| 146 | + |
| 147 | +```php |
| 148 | +use CoderPress\Rest\RestEndpointFacade; |
| 149 | +use CoderPress\Rest\Middleware\MiddlewareFactory; |
| 150 | + |
| 151 | +RestEndpointFacade::createEndpoint( |
| 152 | + namespace: 'api/v1', |
| 153 | + route: 'posts', |
| 154 | + callback: function($request) { |
| 155 | + return ['data' => $request->get_params()]; |
| 156 | + }, |
| 157 | + permissionCallback: function() { |
| 158 | + return true; |
| 159 | + }, |
| 160 | + args: [ |
| 161 | + 'title' => [ |
| 162 | + 'required' => true, |
| 163 | + 'type' => 'string' |
| 164 | + ], |
| 165 | + 'content' => [ |
| 166 | + 'required' => true, |
| 167 | + 'type' => 'string' |
| 168 | + ] |
| 169 | + ], |
| 170 | + methods: ['POST'], |
| 171 | + middlewares: [ |
| 172 | + MiddlewareFactory::sanitization( |
| 173 | + rules: [ |
| 174 | + // Custom sanitization rules for specific fields |
| 175 | + 'title' => fn($value) => sanitize_title($value), |
| 176 | + 'content' => fn($value) => wp_kses_post($value) |
| 177 | + ], |
| 178 | + stripTags: true, |
| 179 | + encodeSpecialChars: true, |
| 180 | + allowedHtmlTags: [ |
| 181 | + 'p' => [], |
| 182 | + 'a' => ['href' => [], 'title' => []], |
| 183 | + 'b' => [], |
| 184 | + 'i' => [] |
| 185 | + ] |
| 186 | + ) |
| 187 | + ] |
| 188 | +); |
| 189 | +``` |
| 190 | + |
| 191 | +The middleware provides: |
| 192 | +- Field-specific sanitization rules using callbacks |
| 193 | +- HTML tag stripping with configurable allowed tags |
| 194 | +- Special character encoding |
| 195 | +- UTF-8 validation |
| 196 | +- Recursive array sanitization |
| 197 | +- WordPress-specific sanitization functions integration |
| 198 | +- XSS and SQL injection protection |
| 199 | + |
| 200 | +All parameters are optional and come with secure defaults. Use the `rules` parameter to define custom sanitization logic for specific fields. |
0 commit comments