Slim 3 - How to add multiple middleware classes on a route? - slim

I know how to add multiple middleware class in $app:
$app->add( new Middlewares\VerifyCsrfToken() );
$app->add( new Middlewares\RemoveTrailingSlash() );
But how on route?
$app->get('/', function($request, $response){
return $response;
})->add( new Middlewares\VerifyCsrfToken() )
->add( new Middlewares\RemoveTrailingSlash() );

As noted in the comments, add() returns an instance of the Route object, so you can simply chain multiple add()s together.
This unit test shows how to do it:
$app = new App();
$app->get('/', function ($req, $res) {
return $res->write('Center');
})->add(function ($req, $res, $next) {
$res->write('In1');
$res = $next($req, $res);
$res->write('Out1');
return $res;
})->add(function ($req, $res, $next) {
$res->write('In2');
$res = $next($req, $res);
$res->write('Out2');
return $res;
});

Related

Slim 4 get all routes into a controller without $app

I need to get all registed routes to work with into a controller.
In slim 3 it was possible to get the router with
$router = $container->get('router');
$routes = $router->getRoutes();
With $app it is easy $routes = $app->getRouteCollector()->getRoutes();
Any ideas?
If you use PHP-DI you could add a container definition and inject the object via constructor injection.
Example:
<?php
// config/container.php
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Interfaces\RouteCollectorInterface;
// ...
return [
App::class => function (ContainerInterface $container) {
AppFactory::setContainer($container);
return AppFactory::create();
},
RouteCollectorInterface::class => function (ContainerInterface $container) {
return $container->get(App::class)->getRouteCollector();
},
// ...
];
The action class:
<?php
namespace App\Action\Home;
use Psr\Http\Message\ResponseInterface;
use Slim\Http\Response;
use Slim\Http\ServerRequest;
use Slim\Interfaces\RouteCollectorInterface;
final class HomeAction
{
/**
* #var RouteCollectorInterface
*/
private $routeCollector;
public function __construct(RouteCollectorInterface $routeCollector)
{
$this->routeCollector = $routeCollector;
}
public function __invoke(ServerRequest $request, Response $response): ResponseInterface
{
$routes = $this->routeCollector->getRoutes();
// ...
}
}
This will display basic information about all routes in your app in SlimPHP 4:
$app->get('/tests/get-routes/', function ($request, $response, $args) use ($app) {
$routes = $app->getRouteCollector()->getRoutes();
foreach ($routes as $route) {
echo $route->getIdentifier() . " → ";
echo ($route->getName() ?? "(unnamed)") . " → ";
echo $route->getPattern();
echo "<br><br>";
}
return $response;
});
From there, one can use something like this to get the URL for a given route:
$routeParser = \Slim\Routing\RouteContext::fromRequest($request)->getRouteParser();
$path = $routeParser->urlFor($nameofroute, $data, $queryParams);
With the following caveats:
this will only work for named routes;
this will only work if the required route parameters are provided -- and there's no method to check whether a route takes mandatory or optional route parameters.
there's no method to get the URL for an unnamed route.

Catching syntax error and custom error reporting

I am using slim framework 3 . I am new to this framework. I am working on catching the errors and returning the custom JSON error and message.
I used this code to catch notFoundHandler error :
$container['notFoundHandler'] = function ($c) {
return function ($request, $response) use ($c) {
return $c['response']
->withStatus(404)
->withHeader('Content-Type', 'application/json')
->write('Page not found');
};
};
But I am able to catch the normal syntax error.
It is showing Warning: fwrite() expects parameter 2 to be string, array given in X-api\controllers\Products.php on line 42
Instead of this message, I want my custom error to handle syntax error reporting.
I used this also,
$container['phpErrorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
//Format of exception to return
$data = [
'message' => "hello"
];
return $container->get('response')->withStatus($response->getStatus())
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
};
But not working for me.
The default error handler can also include detailed error diagnostic information. To enable this you need to set the displayErrorDetails setting to true:
$configuration = [
'settings' => [
'displayErrorDetails' => true,
],
];
$c = new \Slim\Container($configuration);
$app = new \Slim\App($c);
Note this is not appropriate for production applications, since it may reveal some details you would want not to reveal. You can find more in Slim docs.
EDIT
If you need to handle parseErrors, then you need to define phpErrorHandler in your container, just like you did define notFoundHandler.
$container['phpErrorHandler'] = function ($container) {
return function ($request, $response, $error) use ($container) {
return $container['response']
->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong!');
};
};
Note: this will work with PHP7+ only, because in older versions parseErrors cannot be catched.
I have used this short of code in my dependencies.php
$container['errorHandler'] = function ($c) {
return function ($request, $response) use ($c) {
$data = [
'message' => "Syntex error"
];
return $c['response']
->withStatus(200)
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
};
set_error_handler(function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting, so ignore it
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});
Now its working for me.

Getting a menu delivered via REST

I am trying to get a menu via REST and I've created a new module and rest resource plugin that allows for GET on /entity/restmenu/{menu_name}.
I can successfully return this example json using this function when I hit the URL.
public function get(EntityInterface $entity) {
$result = array();
for ($i = 0; $i < 10; $i++) {
$temp = array(
'title' => 'Test ' . $i,
'href' => '#/' . $i
);
array_push($result, $temp);
}
return new ResourceResponse(json_encode($result));
}
I cannot figure out how to load the menu based on $entity. If I hit my URL (http://dang.dev:8888/entity/restmenu/main?_format=hal_json) $entity's value is 'main' which is the machine name of the main menu.
I've tried using Drupal menu tree, but I am not having luck, and debugging this thing with only JSON responses is quite difficult.
How do I get menu item titles and paths based on the menu machine name?
EDIT
Ok, sort of figured it out.
public function get($entity) {
$menu_name = $entity;
$menu_parameters = \Drupal::menuTree()->getCurrentRouteMenuTreeParameters($menu_name);
$tree = \Drupal::menuTree()->load($menu_name, $menu_parameters);
$renderable = \Drupal::menuTree()->build($tree);
$result = array();
foreach (end($renderable) as $key => $val) {
$temp = array(
'menu_item' => $val,
'route' => $key
);
array_push($result, $temp);
}
return new ResourceResponse(json_encode($result));
}
Right now that will output:
[
{
"menu_item":{
"is_expanded":false,
"is_collapsed":false,
"in_active_trail":false,
"attributes":"",
"title":"Home",
"url":{
},
"below":[
],
"original_link":{
}
},
"route":"standard.front_page"
},
{
"menu_item":{
"is_expanded":false,
"is_collapsed":false,
"in_active_trail":false,
"attributes":"",
"title":"Communities",
"url":{
},
"below":[
],
"original_link":{
}
},
"route":"menu_link_content:139d0413-dc50-4772-8200-bc6c92571fa7"
}
]
any idea why url or original_link are empty?
This was the correct answer:
public function get($entity) {
$menu_name = $entity;
$menu_parameters = \Drupal::menuTree()->getCurrentRouteMenuTreeParameters($menu_name);
$tree = \Drupal::menuTree()->load($menu_name, $menu_parameters);
$result = array();
foreach ($tree as $element) {
$link = $element->link;
array_push($result, array(
'title' => $link->getTitle(),
'url' => $link->getUrlObject()->getInternalPath(),
'weight' => $link->getWeight()
)
);
}
return new ResourceResponse(json_encode($result));
}

Test a REST get request

How do I test a GET request of a REST API with PHPUnit 4.1? I use the Slim PHP-Framework and could manage to test the response code but not the body or header.
This is what I have so far:
TestClass:
class AssetTest extends PHPUnit_Framework_TestCase
{
public function request($method, $path, $options = array())
{
// Capture STDOUT
ob_start();
// Prepare a mock environment
Environment::mock(array_merge(array(
'REQUEST_METHOD' => $method,
'PATH_INFO' => $path,
'SERVER_NAME' => 'slim-test.dev',
), $options));
$app = new \Slim\Slim();
$this->app = $app;
$this->request = $app->request();
$this->response = $app->response();
// Return STDOUT
return ob_get_clean();
}
public function get($path, $options = array()){
$this->request('GET', $path, $options);
}
public function testGetAssets(){
$this->get('/asset');
$this->assertEquals('200', $this->response->status());
}
}
If my JSON response of http://example.com/asset looks like this (Code 200):
[
{
"AssetID": "4b0be88b9e853",
"AssetStatusID": "1"
}
]
Everything is good. To get the body of response just call the
$response->getBody() and use json_decode to decode this response. To get the header call the $response->getHeaders().
In your case it will by $this->response->getBody(). So your test
method will be look like this
public function testGetAssets(){
$this->get('/asset');
$response = json_decode($this->response->getBody(), true); //response body
$headers = $this->response->getHeaders() //response headers
$this->assertEquals('200', $this->response->status());
}
This answer is respect to the latest version of guzzlehttp i.e. 6.0

What to do with Zend_Rest_Client_Result?

I'm trying to do searches with the API from last.fm with Zend_Rest_Client.
What am I to do with the response? How do I get the values from the response?
object(Zend_Rest_Client_Result)[226]
protected '_sxml' =>
object(SimpleXMLElement)[228]
public '#attributes' =>
array
'status' => string 'ok' (length=2)
public 'results' =>
object(SimpleXMLElement)[229]
public '#attributes' =>
array
...
public 'trackmatches' =>
object(SimpleXMLElement)[230]
...
protected '_errstr' => null
How do I loop over trackmatches? Everything I try returns null.
$results = $object->getIterator();
foreach($result as $result) {
...
}
Above code will do the magic.
You have to keep dereferencing until you get to something that isn't a SimpleXMLElement object. Trying to print a SimpleXMLElement object doesn't work.
$results = $object->getIterator();
foreach($results->results->trackmatches as $t) {
echo $t->sometagname;
}
I did not manage to be able to do it with Zend. Looks like a useless class like Zend_Http. I had to use the gool 'ol SPL:
$url = $this->host . '?method=track.search';
$url .= '&api_key=' . $this->apikey;
$url .= '&track=' . urlencode($value);
try {
$xmlstr = file_get_contents($url);
$xml = new SimpleXMLElement($xmlstr);
//var_dump($xml->results->trackmatches);
return $xml->results->trackmatches;
} catch (Exception $e) {
echo '<h4>url = ' . $url . '</h4>';
var_dump($e);
}