Use named routes for accessing API locally?


#1

I am using Slim to power an API for a simple online CMS. I need the ability to have a REST API (easy) as well as be able to access data directly from PHP on the website. I am not using Slim to generate any sort of views. Its only there to manage the data inside the CMS.

If I give all of my routes names, will I be able to access my CMS data via named routes?

$app->getContainer()->get('list-blog-posts')

I would expect that to return JSON data.

It would be great if this is possible. This way I could spend my time individual controllers for API inside of duplicating them (one for REST API and another for local PHP includes)


#2

No, it does not work as you expect.

But you can resolve your name to a RouteInterface, e.g.

$route = $app->getContainer()->get(‘router’)->getNamedRoute(‘list-blog-posts’);

and call the run method of the returned route.

Or you could use the subRequest method of the App class.


#3

@joeworkman that is not how Slim works.

  1. Yes, you can use named routes to fetch a “controller”/"action class
  2. No, it will not resolve the data for you.

What you really want is to extract your “Logic” into Domain classes. Attempting to resolve a controller/action in your system is an anti-pattern, which is why we are removing the subRequest in version 4.

In particular you might expose a “UserRepository” to handle the persistence of a User. A UserService for fetching a User in other parts of your system.

One might be able to Generalize this “CRUD” problem by only writing a single Service and Repository if you simply need “CRUD” things and don’t mind using array representation for everything.

class Repository {
    public function store($table, array $data) {
         //... 
    }
}

class DataService {
     public function find($table, $id) {
         //...
     }
}

Of course this design falls apart when you need to do something more complicated than just inserting and fetching rows.


#4

Thanks for the help guys! I had a 99.9% feeling that I was wrong but it never hurts to ask.

@geggleto I was planning a very similar approach to what you described. Although not quite as elegant in my brain as you described :wink:


#5

What if I were to simply have different sets of methods for the CRUD API vs the local PHP API. Where the CRUD Api simply references the local methods.

class DynamicsController
{
	public function __construct($container)
	{
		// init class
	}

	public function list_objects_http($request, $response, $params)
	{
		$id = $request->getAttribute('id');
		return $response->withJson($this->list_objects($id));
	}

	public function list_objects($id)
	{
		$dyn  = new Dynamics($id);
		return $dyn->list_all();
	}

}

Then my route would look like this…

 $this->get('/{id}', 'DynamicsController:list_objects_http')->setName('list-objects');

Then in my I could load the class via local PHP with…

$dc = new DynamicsController()
$objects = $dc->list_objects($id);

#6

That is still not what I would consider to be good coding practice. With 1 more class you create an abstraction that will help you infinitely more.

If you need to use your DynamicsController in a different class; you need to inject it… the problem? the container. You do not want to be passing around a big structure like a container as it has significant memory overhead.

What you should do is Push list_objects into a DynamicsService class which you can pass around your infrastructure. This has the added benefit of

  1. Being able to unit test small pieces of code
  2. Adheres to Single Responsibility (SOLID)
  3. Adheres to Donot Repeat Yourself
  4. Enables you to use Interfaces so you can swap implementations later (like adding a caching layer) also SOLID

#7

Got it! So something like this…

class DynamicsService
{
	public function __construct($container)
	{
		// init class
	}

	public function list_objects($request, $response, $params)
	{
		$id = $request->getAttribute('id');
		$dc  = new DynamicsController();
		return $response->withJson($dc->list_objects($id));
	}

}

class DynamicsController
{
	public function __construct()
	{
		// init class
	}

	public function list_objects($id)
	{
		$dyn  = new Dynamics($id);
		return $dyn->list_all();
	}

}

// CRUD API
$this->get('/{id}','DynamicsService:list_objects')->setName('list-objects');

// local API
$dc = new DynamicsController();
$objects = $dc->list_objects($id);

I will definitely create interfaces for Controller and Services. Probably an abstract class for each as well since there will be multiple related types of Controllers ad Services.

Craziness that my current implementation of all of these does everything inside one class. It all started off so clean. After a couple of years of adding in features, I look up and realize what I had done. :tired_face: