Slim Framework middleware array json manipulation - php

I am trying to implement a simple middleware in Slim Framework that adds an array element in the response body. Without the middleware I am getting correct result as {"mytest":"running"}. What I really want is to have the middle ware merge another element and make it as {"mytest":"running","MODE":"development"}. Instead, I am getting this result {"0":"{\"mytest\"","1":"\"running\"}","MODE":"development"} .
I am definitely missing something very simple. Please have a look below for the code I am using. I guess I am not able to convert the body to an array.
This is what I am doing:
index.php
require 'Vendor/Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
//Middleware
$app->add(new \Slim\Middleware\TestMiddleware());
// Middleware Test
$app->get(
'/mid',
function () use ($app) {
$response = $app->response();
$response->body(json_encode(array(
'mytest'=>'running'
)));
}
);
TestMiddleware.php
namespace Slim\Middleware;
//Appends mode to the response bpoy
class TestMiddleware extends \Slim\Middleware
{
public function call()
{
$app=$this->app;
$this->next->call();
$res=$app->response;
$body=$res->getBody();
$res->setBody(
json_encode(
array_merge(
explode(":",$body),array(
'MODE'=>'development'
)))
);
}
}

Instead of explode try to decode your json before to merge arrays:
json_encode(
array_merge(
json_decode($body,true),
array('MODE'=>'development')
)
)
DEMO

Related

Understanding Slim 4 routing function

I am trying to learn slim framework and I am following the tutorial. What I would like is a detailed explanation of what the be low snippet of code is doing within the slim environment.
$app->get('/client/{name}'
The reason that I am asking is because in keep getting route not found. But I have yet to figure out why. The base route works. But when I added the twig and tried to route to that. It fails.
Now comes the code:
This part is in my webroot/public/index.php
<?php
use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Twig\Error\LoaderError;
require __DIR__ . '/../vendor/autoload.php';
$container = new Container;
$settings = require __DIR__ . '/../app/settings.php';
$settings($container);
AppFactory::setContainer($container);
$app = AppFactory::create();
$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true);
// Create Twig
$twigPath = __DIR__ . "/../templates";
$twig = '';
try {
$twig = Twig::create($twigPath, ['cache' => false]);
} catch (LoaderError $e) {
echo "Error " . $e->getMessage();
}
// Add Twig-View Middleware
$app->add(TwigMiddleware::create($app, $twig));
$routes = require __DIR__ . '/../app/routes.php';
$routes($app);
$app->run();
This part is in the routes.php:
<?php
use Slim\App;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;
return function (App $app) {
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write("Hello world! Really?");
return $response;
});
$app->get('/client/{name}', function (Request $request, Response $response, $args) {
$view = Twig::fromRequest($request);
return $view->render($response, 'client_profiles.html', [
'name' => $args['name']
]);
})->setName('profile');
};
The first route works fine the second does not. According to what I am reading. It should work. https://www.slimframework.com/docs/v4/features/templates.html
I feel that if I knew what get is looking to do. I may be able to fix it and build a proper route.
When I dig into the $app->get which connects with the RouterCollecorProxy.php. There is the $pattern variable and $callable. The callable is the anonymous function that comes after the common in the
$app->get('/client/{name}', function <- this is the callable, right?
I see the map class which takes me to the createRoute which returns the $methods, $pattern, callable and a few other things.
I think the pattern is where my problem is.

Add value to route before in slim framework

Hi Folks i upgrading my slim framework from slim 2 to slim 4 for older project
for one route i added the one value before route using slim 2 slim.before in index.php
example code:
$app->hook('slim.before', function () use ($app) {
$env = $app->environment();
$path = $env['PATH_INFO'];
// spliting the route and adding the dynamic value to the route
$uriArray = explode('/', $path);
$dynamicvalue = 'value';
if(array_key_exists($uriArray[1], array)) {
$dynamicvalue = $uriArray[1];
//we are trimming the api route
$path_trimmed = substr($path, strlen($dynamicvalue) + 1);
$env['PATH_INFO'] = $path_trimmed;
}
});
i read about the add beforemiddleware but cannot able find correct way to add it and i cannot able to find the replacement for $app->environment();
i want to append the dynamic value directly to route
for example
i have one route like this
https://api.fakedata.com/fakeid
by using the above route splitting code i appending the value route using slim.before in slim 2
for example take the dynamic value as test
the route will be
https://api.fakedata.com/test/fakeid
the response of the both api will be same we want to just add value to the route
can any one help me how to do with slim 4
I assume you need to and PATH_INFO to the environment so you can later refer to it in the route callback. You can add a middleware to add attributes to the $request the route callback receives:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class PathInfoMiddleware {
public function __invoke(Request $request, RequestHandler $handler) : Response {
$info = 'some value, path_trimmed for example...'; // this could be whatever you need it to be
$request = $request->withAttribute('PATH_INFO', $info);
return $handler->handle($request);
}
}
// Add middleware to all routes
$app->add(PathInfoMiddleware::class);
// Use the attribute in a route
$app->get('/pathinfo', function(Request $request, Response $response){
$response->getBody()->write($request->getAttribute('PATH_INFO'));
return $response;
});
Now visiting /pathinfo gives the following output:
some value, path_trimmed for example...

Slim 3 middleware validation

I'm trying to implement json-schema validator from justinrainbow as middleware in Slim 3.
I can't figure out how to get the clients input from GET/POST requests in middleware.
tried like this:
$mw = function ($request, $response, $next) {
$data = $request->getParsedBody();
print_r($data); // prints nothing
$id = $request->getAttribute('loan_id');
print_r($id); // prints nothing
// here I need to validate the user input from GET/POST requests with json-schema library and send the result to controller
$response = $next($request, $response);
return $response;
};
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
There are 2 possible ways of how I need it. what i'm doing wrong ?
validate it in middleware and send some array/json response to the controller, which i will then get as I understood with $data = $request->getParsedBody();
validate it in middleware but final check will be in controller like this:
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
if($validator->isValid()){
//
}
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
Best option for me it do something like here
but I don't understand what should i return in container, and how to pass get/post input to container
Your code in the first point seems alright, you only try to access route parameter from within middleware. At that point the route is not yet resolved and therefore parameters are not parsed from the URL.
This is a known use case and is described in Slim's documentation. Add the following setting to your app configuration to get your code working:
$app = new App([
'settings' => [
// Only set this if you need access to route within middleware
'determineRouteBeforeAppMiddleware' => true
]
]);
In order to understand how middleware works and how to manipulate response object, I suggest you read the User Guide - it's not that long and explains it really well.

How to get retuned value from one laravel route into another route

How to get value of one route into another
Route::get('/first_url', function () {
return "Hello this is test";
});
I Tried something like this but not worked.
Route::get('/second_url', function () {
$other_view = Redirect::to('first_url');
});
I want to get returned value from first_url to variable $other_view in second_url to process and manipulate returned value.
Using Redirect is changing url. Which I dont want to use.
Any Idea ??? Or Am I trying wrong thing to do.
If you just want to return first_url, do this:
Route::get('/first_url', ['as' => 'firstRoute', function () {
return "Hello this is test";
}]);
Route::get('/second_url', function () {
return redirect()->route('firstRoute');
});
Learn more about redirects to routes here.
Update:
If you want to pass variable, you can use form or just create a link with parameters. You can try something like this {{ route('second_url', ['param' => 1]) }}
Then your second route will look like this:
Route::get('/second_url/{param}', ['uses' => 'MyController#myMethod', 'param' => 'param']);
And myMethod method in MyController:
public function myMethod($param){
echo $param;
...
I don't know why you would like to do this, but you can get the rendered contents of the route by executing a simple HTTP request to your route and reading the contents:
Route::get('/second_url', function () {
$other_view = file_get_contents(URL::to('first_url'));
return $other_view; // Outputs whatever 'first_url' renders
});
You need to send HTTP request and then process the response. You can use file_get_contents as #tommy has suggested or you can use HTTP library like Guzzle:
$client = new GuzzleHttp\Client();
$res = $client->get(route('firstRoute'));
in this case u should use a named route.
https://laravel.com/docs/5.1/routing#named-routes
somthing like this:
Route::get('/first_url', ['as' => 'nameOfRoute', function () {
return "Hello this is test";
}]);
Route::get('/second_url', function () {
redirect()->route('nameOfRoute');
});
You can not pass the variable value to another route directly. http is stateless protocol. if you want to preserve the value of variable to another route you can do that by 3 methods query string, sessions and cookies only. Your can pass parameter to to specific route like this
Route::get('/second_url/{param}', ['uses' => 'MyController#myMethod',
'param' => 'param']);
The idea behind achieving what you want is naming the function of your first route and calling it within both the first route and your second route. Your function will simply return the view to the first route, and retrieve the rendered html for your second.
function MyFirstRouteFunction() {
// Do whatever your do in your first route
// I assume your function return here an instance of Laravel's View
}
Route::get('/first_url', MyFirstRouteFunction);
Route::get('/second_url', function () {
$contentsOfFirstRoute = MyFirstRouteFunction()->render();
// Make use of rendered HTML
});
There is no need to make one extra HTTP request.
You should use Guzzle or curl to achive this:
Route::get('/second_url', function () {
//:::::Guzzle example:::::
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'http://...second_url...', []);
//:::::curl example:::::
$ch = curl_init();
//define options
$optArray = array(
CURLOPT_URL => 'http://...second_url...',
CURLOPT_RETURNTRANSFER => true
);
//apply those options
curl_setopt_array($ch, $optArray);
//execute request and get response
$res = curl_exec($ch);
});
Note that using Guzzle may need you to install required libraries.
If you put your first_route closure into a controller action you could try to instantiate that controller and call the method directly.
This is considered as bad practice.
routes.php
Route::get('/first_url', 'TestController#getFirstUrl');
App/Http/Controllers/TestController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
public function getFirstUrl()
{
return view('my-view');
}
}
routes.php
Route::get('/second_url', function () {
$controller = new \App\Http\Controllers\TestController();
$contentsOfFirstRoute = $controller->getFirstRoute();
// Make use of rendered HTML
});

Laravel nesting all json responses

I'm using Laravel to create a JSON REST API, and it has been quite present so far. However I'm needing to wrap my JSON outputs with a bit of meta status JSON created by say a metaDataController (or probably model) and I am curious what a good approach to this might be.
For instance, all responses would take on the following format:
{
"meta": {
"status": 200,
"notifications": 2
},
"response": {
//JSON from the route's Controller/Model/etc
}
}
From what I can tell I either need to modify the Laravel Response defaults and delegate to a metaDataController, or create some sort of Route::any that merges the two sections of JSON as mentioned in Returning Multiple Laravel Eloquent Models as JSON. Although I always know metaDataController, the other controller is in flux depending on the route.
I'm thinking there must be a way to declare this structure as a default for all routes or a Route::group.
Thanks!
I don't think doing json_decode->json_encode cycle is an acceptable solution (as in Chris answer).
Here is another solution
use Illuminate\Http\Response;
use Illuminate\Http\Request;
Route::filter('apisuccess', function($route, Request $request, Response $response = null) {
$response->setContent(json_encode([
'data' => $response->original,
'meta' => ['somedata': 'value']
]));
});
Then I would attach this filter to my REST API routes.
Edit: another solution (more complex).
Create a custom Response class:
use Illuminate\Http\Response;
class ApiResponse extends Response
{
protected $meta;
protected $data;
public function __construct($content = '', $status = 200, $headers = array())
{
parent::__construct([], $status, $headers);
$this->meta = [];
}
public function withMeta($property, $value = null)
{
if (is_array($property))
$this->meta = $property;
else
array_set($this->meta, $property, $value);
return $this;
}
public function withData($data)
{
$this->data = $data;
return $this;
}
public function sendContent()
{
echo json_encode(['success' => true, 'data' => $this->data, 'meta' => $this->meta, 'echo' => ob_get_contents()]);
}
}
Put it as a singleton in IOC container:
$this->app->bindShared('ApiResponse', function() {
return new \Truinject\Http\ApiResponse();
});
Finally, create filter and use it as "before" on your routes:
Route::filter('apiprepare', function(Illuminate\Routing\Route $route, Illuminate\Http\Request $request) {
$data = $route->run();
return App::make('ApiResponse')->withData($data);
});
So we are basically overriding default response class with our own, but still calling the appropriate controller with $route->run() to get the data.
To set meta data, in your controller do something like this:
\App::make('ApiResponse')->withMeta($property, $value);
I've added method "meta" in my base API controller class, which encapsulates this.
You could use the global after filter in app.php to catch all responses, then reconfigure it however you please:
App::after(function($request, $response)
{
if(is_a($response, 'Illuminate\Http\JsonResponse')) {
$response->setContent(json_encode(array(
'data' => json_decode($response->getContent()),
'foo' => 'bar',
'cat' => 'dog'
)));
}
});
In the above example, you're taking all the existing json data and putting it in a child data element (this would be "response" in your example) then adding foo and bar. So foo, bar and data would be top level json objects.
If you don't like the global positioning, after is an event sent, so you could also listen to it inside a controller/elsewhere.

Categories