0

My team is developing a R.E.S.T api for our platform.

some examples for endpoints we currently have:

  • campaigns/24566/pictures
  • campaigns/24566/users
  • users/23445646
  • users/campaigns

We currently writing the endpoints in an ugly way for checking the path and we're trying to build an abstract Endpoint class which will include the path to it and validate it.

Our goal is to create this abstract class in order to be able to easily add or remove endpoints just by adding or removing Endpoint classes.

Obviously, this structure of endpoints is screaming for binary tree structure but i'm having some troubles implementing it.

This is my pseudo logic:

  1. The request if for endpoint/sub_endpoint_1/sub_enpoint_5
  2. if there is a class named endpoint and if it can be route - create an instance.
  3. if endpoint has as child named sub_endpoint_1 - create an instance of it
  4. if sub_endpoint_1 has a child named sub_endpoint_5 - create an instance of it
  5. if this is a leaf - run the code in this endpoint

I was thinking about each end point should hold its parents and children but I'm troubles understand how can I first validate the whole path is correct.

If every endpoint can only knows what can before it and what can be after it i might end up with:

endpoint1/sub_endpoint_1/sub_endpoint_5

Would be great for some pseudo guide :)

Asaf Nevo
  • 11,338
  • 23
  • 79
  • 154

1 Answers1

0

I'm not sure if this is what you're asking for, but if I were you I'd use something well-tested and from a well-known framework. Even if you have customly coded PHP application, no frameworks used, you can install only the component(s) you need via composer.

Let's take symfony/ruoting as an example. In order to take HTTP request context it will also require symfony/http-foundation. In order to install both, proceed to your project root directory, install composer if it's not yet installed and then

composer require symfony/http-foundation composer require symfony/routing

then you need to include vendor/autoload.php in your project entry point and that's it, you can use symfony/routing now. The good thing is that since symfony2 is a component framework, you don't need to install all the framework, just those two packages.

Now if you create a test file with the following code:

require_once(realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'));

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$routes = new RouteCollection();
$route = new Route('/{endpoint}/{subendpoint}/{subsubendpoint}');
$route->setDefaults(array(
    'endpoint' => null,
    'subendpoint' => null,
    'subsubendpoint' => null,
));
$routes->add('main', $route);

$context = new RequestContext();

// this is optional and can be done without a Request instance
$context->fromRequest(Request::createFromGlobals());

$matcher = new UrlMatcher($routes, $context);

$parameters = $matcher->match('/hello');
var_dump($parameters);
$parameters = $matcher->match('/');
var_dump($parameters);
$parameters = $matcher->match('/campaigns/24566/pictures');
var_dump($parameters);
$parameters = $matcher->match('/campaigns/24566/users');
var_dump($parameters);
$parameters = $matcher->match('/users/23445646');
var_dump($parameters);
$parameters = $matcher->match('/users/campaigns');
var_dump($parameters);

and try to open it, you will see something like:

array(4) {
  ["endpoint"]=>
  string(5) "hello"
  ["subendpoint"]=>
  NULL
  ["subsubendpoint"]=>
  NULL
  ["_route"]=>
  string(4) "main"
}
array(4) {
  ["endpoint"]=>
  NULL
  ["subendpoint"]=>
  NULL
  ["subsubendpoint"]=>
  NULL
  ["_route"]=>
  string(4) "main"
}
array(4) {
  ["endpoint"]=>
  string(9) "campaigns"
  ["subendpoint"]=>
  string(5) "24566"
  ["subsubendpoint"]=>
  string(8) "pictures"
  ["_route"]=>
  string(4) "main"
}
array(4) {
  ["endpoint"]=>
  string(9) "campaigns"
  ["subendpoint"]=>
  string(5) "24566"
  ["subsubendpoint"]=>
  string(5) "users"
  ["_route"]=>
  string(4) "main"
}
array(4) {
  ["endpoint"]=>
  string(5) "users"
  ["subendpoint"]=>
  string(8) "23445646"
  ["subsubendpoint"]=>
  NULL
  ["_route"]=>
  string(4) "main"
}
array(4) {
  ["endpoint"]=>
  string(5) "users"
  ["subendpoint"]=>
  string(9) "campaigns"
  ["subsubendpoint"]=>
  NULL
  ["_route"]=>
  string(4) "main"
}

If you then integrate it into your REST API entry script, you can pass $_SERVER['REQUEST_URI'] into ::match() method and then something like:

if(!empty($parameters['subsubendpoint'])) {
    //instantiate the "leaf"-subclass
} elseif(!empty($parameters['subendpoint'])) {
    //instantiate the "middle"-subclass
} else {
    //instantiate the "main" class
}
Alexey
  • 3,414
  • 7
  • 26
  • 44
  • thanks for that detailed answer. We dont use this framework but our current logic is similar. Im trying to create and abstract generic class built as binary tree to be able to take care of all requests. Your example handles more of giving names to different parts of the URI and reference them later - which is what im doing now. – Asaf Nevo Jun 27 '15 at 14:29