MODSlim was intended to run MODX requests through Slim (and PSR7) in order to be able to make use of an app server that keeps code in memory and simply handles PSR7 requests as they come in. However, loading MODX in any Slim request is fairly simple and I personally would much rather build a REST API in Slim rather than in a CMS. I’ve built a number of REST APIs in Slim 3.x, but never one that connected to MODX, so I don’t have any specific examples ready for you, but I’d be happy to craft some if you need some assistance getting MODX working in your DI container. I’ve also built some stuff in Slim 4.x recently and getting going in 4.x would be similar effort if you’ve never created something with either.
If you’d like to run MODX behind Slim, such middleware or a notFound handler in Slim is pretty simple to accomplish. The rest of your endpoints can then be plain Slim (with access to the MODX/xPDO if needed through the container, like Jason says).
If you don’t want MODX behind Slim, you could also simply setup a separate folder for the API and route requests to that folder to Slim instead. Still with MODX/xPDO in the container to access models and settings if needed, but no need to route one through the other in that case.
Slim/custom API routes inside MODX… now there’s a dream to figure out some day The modRestServer stuff is cool for a quick CRUD API for xPDO models, but it’s also a bit restrictive in not supporting subresources and such.
No doubt being able to use MODX as a manager for Slim routing would be awesome. This is exactly why the original MAB proposal to reconstruct MODX on top of Slim was conceived.
I guess, what I want, is replacing the modRestServer stuff, which I’m using right now in my Extra, with a Slim based approach.
I want to manage the routing with MIGX - configurations, where I’ve stored classnames, joins, permissions, hooks and what not.
This is my first attempt with Slim 4, which basically seems to work.
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$working_context = 'web';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize($working_context);
$base_path = MODX_ASSETS_URL . 'components/slimtest';
//$_SERVER['REQUEST_URI'] = str_replace('/assets/components/slimtest/','',$_SERVER['REQUEST_URI']);
// Should be set to 0 in production
error_reporting(E_ALL);
// Should be set to '0' in production
ini_set('display_errors', '1');
$app = AppFactory::create();
$app->setBasePath($base_path);
$app->get('/resources/{id}', function (Request $request, Response $response, $args) {
global $modx;
$result = [];
$status = 200;
if ($resource = $modx->getObject('modResource',$args['id'])) {
$result = ['result' => $resource->toArray()];
} else {
$result = ['error' => ['message' => 'Resource not found']];
$status = 404;
}
$response->getBody()->write(json_encode($result,JSON_PRETTY_PRINT));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($status);
});
$app->run();
however, global $modx doesn’t feel right.
I guess, my question right now is, what is the right way, to bring MODX to the party in that case.
Thanks @markh use did work, if I work with a Closure, but when I try to use a Action - Class, I need to pass it into a container.
My experimental scenario looks like that now, which seems to work so far:
\core\components\slimtest\public\index.php
<?php
use DI\Container;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
require __DIR__ . '/../vendor/autoload.php';
// Create Container using PHP-DI
$container = new Container();
// Set container to create App with on AppFactory
AppFactory::setContainer($container);
$working_context = 'web';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize($working_context);
$container->set('modx', function () use ($modx) {
return $modx;
});
$base_path = MODX_ASSETS_URL . 'components/slimtest';
// Should be set to 0 in production
error_reporting(E_ALL);
// Should be set to '0' in production
ini_set('display_errors', '1');
$app = AppFactory::create();
$app->setBasePath($base_path);
$app->get('[/{route}/{key}]',\App\Action\DefaultActions\ReadAction::class);
$app->run();
I’d put the bit inside of the $container->set() callback rather than the use syntax for a global $modx and probably suggest using the class name instead of a named string:
$container->set(modX::class, function () {
$working_context = 'web';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize($working_context);
return $modx;
});
… but beyond that I think that’s pretty much how I’ve done it on projects as well.
At some point a project and container grows that you start initialising it differently to keep it more strucctured than a single big index.php, e.g. like this with a custom App class…
Or you might choose to start making use of PHP-DIs autowiring so instead of pulling dependencies out of the container in the constructor, your constructor just looks like this instead:
class ReadAction {
public function __construct(modX $modx)
{
$this->modx = $modx;
}
// ...
}
That should just work with the current way you’re defining the route, as long as you adjust the $container->set() call to use the class name.