klezbucket/cakephp-klez-api

KlezApi plugin for CakePHP

0.5-beta 2019-05-13 01:35 UTC

This package is auto-updated.

Last update: 2022-11-13 09:36:01 UTC


README

Welcome to the KlezPlugin's family.

We aim to automatize and make easy the repetitive parts of any project. Extending the framework to provide the hooks and settings to do so.

Changelog v0.5-beta

The ImplementationNode is here, implements an abstract pipeline.

Changelog v0.4-beta

The PreflightNode is here, ready to serve CORS requests.

Concretize abstract pipelines, factor out common pipeline sections. Avoid configuration bloatness.

Changelog v0.3-beta

Multi-pipeline execution flow implemented. Jump across pipelines within the new API.

Pipeline halting implemented. Stop the current pipeline execution through the new API.

Changelog v0.2-beta

Logging capabilities implemented.

Changelog v0.1-beta

This is the first commit.

This package covers all the Api necessities the project may need.

Formatter Support

  • json
  • xml
  • yml
  • php

Installation

You can install this plugin into your CakePHP application using composer.

The recommended way to install composer packages is:

composer require klezbucket/cakephp-klez-api

Routing

You should hook the plugin's routes on the desired scope. Example:

Router::scope('/', function (RouteBuilder $routes) {
    $routes->scope('/api', [ 'plugin' => 'KlezApi' ], function (RouteBuilder $routes) {
        $routes->loadPlugin('KlezApi');
    });
});

Afterwards, the whole API hangs on this route:

$routes->connect('/:endpoint.:format', ['controller' => 'Webservice', 'action' => 'index' ]);

Valid route example: /api/test.json

The key part of the API is the (endpoint,format) duple. The example above denotes the 'test' endpoint, as 'json' data.

Bootstrap

Avoid config/bootstrap.php for loading plugins.

Instead put the code below into your src/Application.php bootstrap() method.

        $this->addPlugin('KlezApi', [
            'routes' => false,
            'log' => [
                'enabled' => true,
                'maxlength' => 255
            ],
            'config' => [
                'endpoints' => 'endpoints'
            ]
        ]);

The config.endpoints entry denotes a CakePHP config file inside the /config folder. The log entry denotes the logging config.

Logging Config

The Logging engine requires the KlezCore's Log adapter. The log's config has the following options:

  • enabled: boolean that enables the logging adapter. Defaults to false.
  • maxlength: integer which dictates the max length of any given log message. Defaults to 255.
  • file: string for the log's filename. Default to 'klezapi'
  • scopes: array for any context for the log adapter. Defaults to ['klezapi']

Endpoints Config File

As any CakePHP config file, it should return and assoc array.

Each key in this array should follow the next convention: 'KlezApi.endpoints.{endpoint}', where {endpoint} stands for the endpoint's name denoted by its route.

There are two kind of settings, the 'individual file' one, and the 'bloated' one.

'Individual file' example: we denote a file relative to the /config folder, the plugin will search for the file and load the config from there.

This file should follow the 'bloated' syntax.

	"KlezApi.endpoints.test" => "endpoints/test",

'Bloated' example, we put everything in the same config file.

	"KlezApi.endpoints.test2" => [
		"config" => [
			"increment" => -3,
		],
		"pipeline" => [
			0 => "KlezApi\\Controller\\Node\\CounterNode",
			1 => "KlezApi\\Controller\\Node\\CounterNode",
			2 => "KlezApi\\Controller\\Node\\CounterNode",
			3 => "KlezApi\\Controller\\Node\\CounterNode",
			4 => "KlezApi\\Controller\\Node\\CounterNode",
			5 => "KlezApi\\Controller\\Node\\FormatterNode",
		],
	],

The key components are the config and pipeline arrays.

'Config' stands for every hardcoded setting the endpoint may require, as additional input.

'Pipeline' stands for the set of 'Nodes' the plugin should execute, in order, to process and generate the output.

The 'pipeline' key correspond to the main pipeline which denotes the entry point fot the endpoint's execution flow. However, its also possible to jump across pipelines.

To specify another pipeline simply give it a different name and put its node collection in the same assoc array, example:

	"KlezApi.endpoints.test2" => [
		"config" => [
			"increment" => -3,
		],
		"pipeline" => [
			0 => "YourApp\\Controller\\Node\\SomeJumpingNode",
			1 => "KlezApi\\Controller\\Node\\FormatterNode",
		],
		'another_pipeline' => [
			0 => "KlezApi\\Controller\\Node\\CounterNode",
			1 => "KlezApi\\Controller\\Node\\CounterNode",
			2 => "KlezApi\\Controller\\Node\\CounterNode",
			3 => "KlezApi\\Controller\\Node\\CounterNode",
			4 => "KlezApi\\Controller\\Node\\CounterNode",
		]
	],

In this example 'another_pipeline' is specified.

Pipeline and Nodes

Each endpoint has at least one collection of Nodes, referred as Pipelines. Each Node should implement the 'Node' interface to honor the execution flow denoted by its pipeline.

The pipeline provides a common buffer between every Node, the data can be stored and pivoted through it.

The buffer API includes read(), write(), overwrite() and delete() methods within the Node interface.

Jumping across Pipelines

In the example above, an alternative pipeline is specified.

In order to jump into this new pipeline, a Node with the main pipeline must call the jump() method. The parameter of this method must be the name of the desired pipeline.

        $this->jump('another_pipeline');

If the pipeline is not specified into the endpoint's assoc array, it's resolved into an empty pipeline rather than raising an error.

For further explanations, the pipeline calling jump() will be referred as the 'calling pipeline'.

Halting the Current Pipeline

If for some reason is not desired to continue the current pipeline execution, the halt() method stops further node evaluation.

        $this->halt();

Note if you call halt() into your Node and immediately a jump() is called, this jump does not take any effect since the current pipeline is being halted.

Halting the current pipeline backtracks the execution flow to the calling pipeline, if any.

Abstract Pipelines

Sometimes several endpoints may have common execution paths, copy and pasting the nodes should work, but is not that tasteful.

Abstract pipelines can be used to specify such common paths. Furthermore, these abstract pipelines aren't exposed into the API routing, so they are kept hidden until you concretize them.

The concretize() method allows this. It requires an argument, the abstract's pipeline name.

        $this->concretize('my_abstract_pipeline');

Abstract pipelines configuration are exactly the same as usual pipelines, but they are specified by the KlezApi.abstract.{$name} key, inside the endpoint's assoc array.

    "KlezApi.abstracts.triple_sum_by_eleven" => [
        "config" => [
            "increment" => 11,
        ],
        "pipeline" => [
            0 => "KlezApi\\Controller\\Node\\CounterNode",
            1 => "KlezApi\\Controller\\Node\\CounterNode",
            2 => "KlezApi\\Controller\\Node\\CounterNode",
        ],
    ],
    "KlezApi.endpoints.abstract" => [
        "config" => [
            'preflight' => [
                'Access-Control-Allow-Origin' => 'klezkaffold3.klez'
            ],
            'concretize' => 'triple_sum_by_eleven'
        ],
        "pipeline" => [
            0 => "KlezApi\\Controller\\Node\\PreflightNode",
            1 => "KlezApi\\Controller\\Node\\ConcretizeTestNode",
            2 => "KlezApi\\Controller\\Node\\FormatterNode",
        ],
    ],

In the above example, an abstract pipeline called 'triple_sum_by_eleven' is specified. The abstract endpoint is also specified. Note that the 'ConcretizeTestNode' inside the endpoint's pipeline calls for concretize(), with the 'concretize' argument denoted in its config. Therefore, the 'triple_sum_by_eleven' abstract is concretized and jumped into.

If several alternative pipelines are specified into the abstract, they are also concretized, but the jump is performed upon the main pipeline.

Core Nodes

We'll try to provide basic Nodes which solves plenty of common problems within any webservice development.

In the Node suite provided there are also many Testing Node that don't make any sense in a webservice development context, but are rather useful for testing purposes.

The Formatter Node

The 'FormatterNode' as you can see as the last element in the pipeline example above, is a core Node. Its main function is to provide the output, formatted as the 'format' its route dictates.

This Node takes its current buffer and serializes it into the desired format, before sending the output.

The Jump Method Node

The 'JumpMethodNote' jump to the pipeline denoted by the current http method being requested in lowercase form.

    "KlezApi.endpoints.test" => [
        "config" => [

        ],
        "pipeline" => [
            0 => "KlezApi\\Controller\\Node\\RandomIncrementNode",
            1 => "KlezApi\\Controller\\Node\\JumpMethodNode",
            2 => "KlezApi\\Controller\\Node\\FormatterNode",
        ],
        'get' => [
            0 => "KlezApi\\Controller\\Node\\BufferCounterNode",
        ],
        'post' => [
            0 => "KlezApi\\Controller\\Node\\BufferCounterNode",
            1 => "KlezApi\\Controller\\Node\\BufferCounterNode",
        ],
        'put' => [
            0 => "KlezApi\\Controller\\Node\\BufferCounterNode",
            1 => "KlezApi\\Controller\\Node\\BufferCounterNode",
            2 => "KlezApi\\Controller\\Node\\BufferCounterNode",
        ],
    ],

In this example all the GET request will be commuted into the 'get' pipeline, the same holds true for each method specified in the endpoint's assoc array. If a method is not specified, then it's resolved as an empty pipeline, so nothing happens.

The Preflight Node

The 'PreflightNode' serves CORS headers when an OPTIONS method is requested. If such method is detected the configured headers are served, default values are passed by if none are specified. This node will halt the pipeline when serving the CORS headers.

    "KlezApi.endpoints.cors" => [
        "config" => [
			'headers => [
				'Access-Control-Allow-Headers' => 'Accept,Authorization,Content-Type'
			]
        ],
        "pipeline" => [
            0 => "KlezApi\\Controller\\Node\\PreflightNode",
            1 => "KlezApi\\Controller\\Node\\RandomIncrementNode"
            2 => "KlezApi\\Controller\\Node\\JumpMethodNode",
            3 => "KlezApi\\Controller\\Node\\FormatterNode",
        ],
        'options' => [
            0 => "KlezApi\\Controller\\Node\\BufferCounterNode",
        ],
        'get' => [
            0 => "KlezApi\\Controller\\Node\\BufferCounterNode",
            1 => "KlezApi\\Controller\\Node\\BufferCounterNode",
        ],
    ],

In this example, when an OPTIONS method is requested, the 'PreflightNode' will serve the 'Access-Control-Allow-Headers' configured, and will attach the default headers missing, 'Access-Control-Allow-Methods' and 'Access-Control-Allow-Origin'. If more headers are needed, they can also be appended into the configuration assoc array. Afterwards, the pipeline will be halted. And thus, the 'options' pipeline specified will never be executed, since the execution flow won't reach the 'JumpMethodNode'.

The Implementation Node

The 'ImplementationNode' implements and abstract pipeline, it reads from the config.implements the desired pipeline.

    "KlezApi.abstracts.main" => [
        "pipeline" => [
            0 => "KlezApi\\Controller\\Node\\PreflightNode",
            1 => "KlezApi\\Controller\\Node\\ImplementationNode",
            2 => "KlezApi\\Controller\\Node\\FormatterNode",
        ],
    ],
    "KlezApi.abstracts.auth" => [
        "pipeline" => [
            0 => "App\\Controller\\Node\\CaptchaNode",
            1 => "App\\Controller\\Node\\AuthNode",
        ],
    ],
    "KlezApi.endpoints.auth" => [
        "config" => [
            'implements' => 'auth'
        ],
        "pipeline" => [
            0 => "App\\Controller\\Node\\MainNode", // MainNode runs $this->concretize('main');
        ],
    ],

In the above example, the 'auth' endpoint implements the abstract 'main' pipeline, it surrounds another ImplementationNode between common feture Nodes. This ImplementationNode implements the 'auth' abstract pipeline which holds the app authorization logic.

This allows to create a common pipeline section, the 'main' abstract pipeline. And then specify the business related logic, the 'auth' abstract pipeline.

Commands

Generate a Bare Endpoint

bin/cake KlezApi.generator newstuff

Creates a new endpoint entry in the endpoints config file.

The example above appends the following entry into the config array:

	"KlezApi.endpoints.newstuff" => [
		"config" => [
		],
		"pipeline" => [
			0 => "KlezApi\\Controller\\Node\\FormatterNode",
		],
	],

It checks if the endpoint already exists. In such case, the endpoint config won't be replaced and the command will fail.