Slim Framework show info when using old api version - slim

I am following this guide for using Slim Groups to version my API. I just wanted to know if it is possible to capture all calls against an old version without the need to do this for every function. Like a wildcard, you know?
E.g. I updated from v1 to v2 so the call: myapi.com/v1/user/1 should return: You are using an old API version.
But I don't want to do it like this (for every function):
$app->group('/v1', function () use ($app) {
$app->group('/user', function () use ($app) {
$app->get('/:id', function ($id) {
echo "You are using an old version.";
});
});
});
But more like this (that he ignores all subparts and parameters):
$app->get('/v1/*', function ($id) {
echo "You are using an old version.";
});
This is just a private API that only my App that will know how to handle this will use so please don't care about backward compatibility ^^

If you want to return just the message for any /v1/anything/can/be/here routes, then you could use wildcard routes (docs).
$app->get('/v1/:anything+', function () use ($app) {
$app->halt(400, 'You are using old API');
});
If you want to keep the old API still working, but want to modify the response to include the message, use group middleware.
<?php
$app->group('/v1', function () use $(app) {
// this is the middleware
echo 'You are using old API';
// $app->stop(); // uncomment to stop HERE
}, function () use ($app) {
$app->get('/:id', function ($id) {
// logic for the route
});
});

I used:
$app->group('/v1', function() {
$this->any('', function ($request, $response, $args) {
return $response->withJson(
array("error" => TRUE, "msg" => "You are using old API"),
400 // status code
);
});
});

Related

How to access to Slim container from other classess?

Suppose that in my dependencies.php file I setup this container:
<?php
$container = $app->getContainer();
$container['db'] = function($config)
{
$db = $config['settings']['db'];
$pdo = new PDO("mysql:host=" . $db['host'] . ";port=" . $db['port'] .
";dbname=" . $db['dbname'] . ";charset=" . $db['charset'], $db['user'], $db['pass']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
};
I can use this container inside my route, called for example table.php:
<?php
use Slim\Http\Request;
use Slim\Http\Response;
$app->get('/table/get_table', function (Request $request, Response $response, array $args)
{
$sql = $this->db->prepare("SELECT * FROM some_table");
$sql->execute();
$result = $sql->fetchAll();
return $response->withJson($result);
});
this is basically the default usage, right? Said that, how can I instead use the db container from other classes? Supposes I have created a class called TableUtility and imported inside the table.php:
class TableUtility
{
function GetTableFromDb()
{
$sql = $this->db->prepare("SELECT * FROM some_table");
$sql->execute();
$result = $sql->fetchAll();
return $response->withJson($result);
}
}
as you can see I moved the logic of PDO inside GetTableFromDb of TableUtility, how can I access to db container from this class?
The usage in table.php will be:
<?php
use Slim\Http\Request;
use Slim\Http\Response;
$app->get('/table/get_table', function (Request $request, Response $response, array $args)
{
$tableUtility = new TableUtility();
return $response->withJson($tableUtility->GetTableFromDb());
});
actually I get in TableUtility:
Call to a member function prepare() on null
The full name for what you refer to as container is Dependency Injection Container. It is supposed to contain dependencies for objects. Passing this container to objects is considered bad practice. Instead you should pass only required dependencies for that object, which in your case is to pass db to $tableUtility. This is usually used by passing dependencies when constructing the object, or using setter methods. In your case you can refactor your code like this:
class TableUtility
{
function __construct($db) {
$this->db = $db;
}
}
Now in any method of TableUtility class, you have access to db object using $this->db but you'll need to pass db to class constructor whenever you create a new object. So you also need to do this:
$app->get('/table/get_table', function (Request $request, Response $response, array $args)
{
$tableUtility = new TableUtility($this->db);
// rest of the code
});

Perl Dancer2 infinite-loop on get when calling a method

I made a photobooth with Dancer some years ago and it worked fine.
Now I try to move this to Dancer2. However, It's not working anymore, because I have some infinite-loop.
Let's say my app looks like this:
package App;
use Dancer2;
# Photobox is my pm file with all the magic
use Photobox;
my $photobox = photobox->new();
get '/photo' => sub {
my $photo;
# Trigger cam and download photo. It returns me the filename of the photo.
$photo = $photobox->takePicture();
# Template photo is to show the photo
template 'photo',
{
'photo_filename' => $photo,
'redirect_uri' => "someURI"
};
}
takePicture() looks like this:
sub takePicture {
my $Objekt = shift;
my $return;
my $capture;
$return = `gphoto2 --auto-detect`;
if ($return =~ m/usb:/) {
$capture = `gphoto2 --capture-image-and-download --filename=$photoPath$filename`;
if (!-e $photoPath.$filename) {
return "no-photo-error.png";
}
else {
return $filename;
}
} else {
die "Camera not found: $return";
}
}
When I now call /photo, it'll result in an infinite loop. The browser is "frefreshing" all the time and my cam is shooting one photo after the other. But it never redirects to /showphoto.
It was working with Dancer(1) when I run the application just by perl app.pl from the bin directory. How I use Dancer2 und run it by using plackup app.psgi
I tried to put it into a before hook, but it changed nothing.
Update:
I figured out a way to work around this issue.
First I refactored my code a bit. Basic idea was to split the take photo and show photo operations into two different routes. This makes it easier to see what happens.
get '/takesinglephoto' => sub {
my $photo;
$photo = takePicture();
$single_photo=$photo;
redirect '/showsinglephoto';
;
get '/showsinglephoto' => sub {
set 'layout' => 'fotobox-main';
template 'fotobox_fotostrip',
{
'foto_filename' => $single_photo,
'redirect_uri' => "fotostrip",
'timer' => $timer,
'number' => 'blank'
};
};
And I moved the takePicture method just into my Dancer main App.pm.
Now I recognized from the log output, that the browser does not load the '/takesinglephoto' page once, but refreshes it every some secons. I think the reason is, that takePicture() takes some seconds to run and to return the output. And Dancer does not wait until it ends. With every reload, it triggers the takePicture() again and that causes the infinite-loop.
I worked around this by implementing a simple check to run takePicture() just once.
# define avariable set to 1 / true
my $do_stuff_once = 1;
get '/takesinglephoto' => sub {
my $photo;
# check if variable is true
if ($do_stuff_once == 1) {
$photo = takePicture();
$single_photo=$photo;
# set variable to false
$do_stuff_once = 0;
}
redirect '/showsinglephoto';
};
get '/showsinglephoto' => sub {
# set variable back to true
$do_stuff_once = 1;
set 'layout' => 'fotobox-main';
template 'fotobox_fotostrip',
{
'foto_filename' => $single_photo,
'redirect_uri' => "fotostrip",
'timer' => $timer,
'number' => 'blank'
};
};
Now it still refreshes /takesinglephoto, but it does not trigger takePicture() again and again and finally, when the method returns the photo filename, it redirects to /showsinglephoto.
I would call this a workaround. Is there a better way to solve this?
BR
Arne

mojolicious helper storing an elasticsearch connection

i'm experimenting with elasticsearch within mojolicious.
I'm reasonably new at both.
I wanted to create a helper to store the ES connection and I was hoping to pass the helper configuration relating to ES (for example the node info, trace_on file etc).
If I write the following very simple helper, it works;
has elasticsearch => sub {
return Search::Elasticsearch->new( nodes => '192.168.56.21:9200', trace_to => ['File','/tmp/elasticsearch.log'] );
};
and then in startup
$self->helper(es => sub { $self->app->elasticsearch() });
however if I try to extend that to take config - like the following -
it fails. I get an error "cannot find index on package" when the application calls $self->es->index
has elasticsearch => sub {
my $config = shift;
my $params->{nodes} = '192.168.56.21:' . $config->{port};
$params->{trace_to} = $config->{trace_to} if $config->{trace_to};
my $es = Search::Elasticsearch->new( $params );
return $es;
};
and in startup
$self->helper(es => sub { $self->app->elasticsearch($self->config->{es}) });
I assume I'm simply misunderstanding helpers or config or both - can someone enlighten me?
Just fyi, in a separate controller file I use the helper as follows;
$self->es->index(
index => $self->_create_index_name($index),
type => 'crawl_data',
id => $esid,
body => {
content => encode_json $data,
}
);
that works fine if I create the helper using the simple (1st) form above.
I hope this is sufficient info? please let me know if anything else is required?
First of all, has and helper are not the same. has is a lazily built instance attribute. The only argument to an attribute constructor is the instance. For an app, it would look like:
package MyApp;
has elasticsearch => sub {
my $app = shift;
Search::ElasticSearch->new($app->config->{es});
};
sub startup {
my $app = shift;
...
}
This instance is then persistent for the life of the application after first use. I'm not sure if S::ES has any reconnect-on-drop logic, so you might need to think about it a permanent object is really what you want.
In contrast a helper is just a method, available to the app, all controllers and all templates (in the latter case, as a function). The first argument to a helper is a controller instance, whether the current one or a new one, depending on context. Therefore you need to build your helper like:
has (elasticsearch => sub {
my ($c, $config) = #_;
$config ||= $c->app->config->{es};
Search::ElasticSearch->new($config);
});
This mechanism will build the instance on demand and can accept pass-in arguments, perhaps for optional configuration override as I have shown in that example.
I hope this answers your questions.

Slim Framework - Using a GET route

I'm absolutely new to Slim Framework. I'm working on an Webservice that should provide an interface between an Android App and a Web-Application. I used the Slim Documentation to make my first steps and now I want to create a simple GET route, to receive information from the App. Here is what I have so far:
require 'Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$name_outside = '';
$app = new \Slim\Slim();
$app->get('/session/program_name/:name', function ($name) use($app) {
$name_outside = $name;
echo $name;
});
$app->run();
echo $name_outside;
I need to access the variable :name outside the function, but what I get is nothing. What I am doing wrong here?
Btw: I know that GET-routes usually are used to list existing resources, but for my simple case, I decided to use it that way.
Fix your code to hold name as args parameters ,then you can get in in Your function
require 'Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$name_outside = '';
$app = new \Slim\Slim();
$app->get('/session/program_name/{name}', function ($args) use($app) {
$name_outside = $args['name'];
echo $args['name'];
});
$app->run();
To acess the $name_outside inside the function context you pass it.
$app->get('/session/program_name/:name', function ($name) use($app, &$name_outside) {
$name_outside = $name;
echo $name;
});
But perhaps you're using Slim in a wrong way. Why you have to access the variable outside of your route?
No code is execyted after the run() call. That's the way slim works, try to put a die where you're echo the variable, it isn't reachable.
You shouldn't have the need to access the context of the route outside of it this way. To transform a request or a response you use middlewares with the hooks.

How to match a result to a request when sending multiple requests?

A. Summary
As its title, Guzzle allows to send multiple requests at once to save time, as in documentation.
$responses = $client->send(array(
$requestObj1,
$requestObj2,
...
));
(given that each request object is an instance of
Guzzle\Http\Message\EntityEnclosingRequestInterface)
When responses come back, to identify which response is for which request, we can loop through each request and get the response (only available after executing the above command):
$response1 = $requestObj1->getResponse();
$response2 = $requestObj2->getResponse();
...
B. Problem
If the request object contains the same data. It's impossible to identify the original request.
Assume we have the following scenario where we need to create 2 articles: A and B on a distance server: something.com/articles/create.json
Each request has same POST data:
subject: This is a test article
After created, the Guzzle responses with 2 location come back:
something.com/articles/223.json
something.com/articles/245.json
Using the above method to link response-to-its-request, we still don't know which response is for which article, because the request object is exactly the same.
Hence in my database I cannot write down the result:
article A -> Location: 245.json
article B -> Location: 223.json
because it can be the other way arround:
article A -> Location: 223.json
article B -> Location: 245.json
A solution is to put some extra parameter in the POST request, e.g.
subject: This is a test article
record: A
However, the distance server will return error and does not create article because it does not understand the key "record". The distance server is a third party server and I cannot change the way it works.
Another proper solution for this is to set some specific id/tag on the request object, so we can identify it afterwards. However, I've looked through the documentation but there is no method to uniquely identity the request like
$request->setID("id1")
or
$request->setTag("id1")
This has been bugging me for months and still cannot resolve this issue.
If you have solution, please let me know.
Many many thanks and you've saved me!!!!
Thanks for reading this long post.
I've found a proper way to do it, Guzzle allow to add callback once a request is completed. So we can achieve this by setting it on each request in the batch
Each request by default can be created like this
$request = $client->createRequest('GET', 'http://httpbin.org', [
'headers' => ['X-Foo' => 'Bar']
]);
So, to achieve what we want:
$allRequests = [];
$allResults = [];
for($k=0; $k<=10; $k++){
$allRequests['key_'.$k] = $client->createRequest('GET', 'http://httpbin.org?id='.$k, [
'headers' => ['X-Foo' => 'Bar'],
'events' => [
'complete' => function ($e) use (&$allResults, $k){
$response = $e->getResponse();
$allResults['key_'.$k] = $response->getBody().'';
}
]
]);
}
$client->sendAll(array_values($allRequests));
print_r($allResults);
So now the $allResults has result for each corresponding request.
e.g. $allResults['key_1'] is the result of $allRequests['key_1']
I was having the same problem with this.
I solved it by adding a custom query parameter with a unique id generated for each request and add it to the request url (you will need to remember this ids for each one of them to address it after).
After $responses = $client->send($requests) you could iterate through the responses and retrieve the effective url $response->getEffectiveUrl() and parse it (see parse_url and parse_str) to get the custom parameter (with the unique id) and search in your array of requests which one has it.
I found a much better answer.
I was sending batches of 20 requests at a time, 4 concurrently, and used the pooling technique where I got fulfilled, and rejected back, as in the documentation.
I found that I could add this code to the end of my requestAsync() function calls, when yielding / building the array (I do both in different places).
$request = $request->then(function (\GuzzleHttp\Psr7\Response $response) use ($source_db_object) {
$response->_source_object = $source_db_object;
return $response;
});
And then in the clousures on the pool, I can just access the _source_object on the response normally, and it works great.
I find it a little hacky, but if you are just sure to use a name that NEVER clashes with anything in Guzzle, this should be fine.
Here is a full example:
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Response as GuzzleResponse;
$client = new Client();
$requests = [];
// Simple set-up here, generate some random async requests
for ($i = 0; $i < 10; $i++) {
$request = $client->requestAsync('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// Here we can attach any identifiable data
$request->_source_object = $i;
array_push($requests, $request);
}
$generator = function () use($requests) {
while ($request = array_pop($requests)) {
yield function() use ($request) {
return $request->then(function (GuzzleResponse $response) use ($request) {
// Attach _source_object from request to the response
$response->_source_object = $request->_source_object ?? [];
return $response;
});
};
}
};
$requestPool = new Pool($client, $generator(), [
'concurrency' => 5,
'fulfilled' => function ($response) {
// Then we can properly access the _source_object data once response has arrived here!
echo $response->_source_object . "\n";
}
]);
$requestPool->promise()->wait();
I do it this way :
// create your requests
$requests[] = $client->createRequest('GET', '/endpoint', ['config' => ['order_id' => 123]]);
...
// in your success callback get
$id = $event->getRequest()->getConfig()['order_id']
An update related to the new GuzzleHttp guzzlehttp/guzzle
Concurrent/parallel calls are now run through a few different methods including Promises.. Concurrent Requests
The old way of passing a array of RequestInterfaces will not work anymore.
See example here
$newClient = new \GuzzleHttp\Client(['base_uri' => $base]);
foreach($documents->documents as $doc){
$params = [
'language' =>'eng',
'text' => $doc->summary,
'apikey' => $key
];
$requestArr[$doc->reference] = $newClient->getAsync( '/1/api/sync/analyze/v1?' . http_build_query( $params) );
}
$time_start = microtime(true);
$responses = \GuzzleHttp\Promise\unwrap($requestArr); //$newClient->send( $requestArr );
$time_end = microtime(true);
$this->get('logger')->error(' NewsPerf Dev: took ' . ($time_end - $time_start) );
In this example you will be able to refer to each of the Responses using $requestArr[$doc->reference] . In short give an index to your array and run the Promise::unwrap call.
I also had come across this issue. This was the first thread coming up. I know this is a resolved thread, but I have eventually come up with a better solution. This is for all those who might encounter the issue.
One of the options is to use Guzzle Pool::batch.
What batch does is, it pushed the results of pooled requests into an array and returns the array. This ensures that the response and requests are in the same order.
$client = new Client();
// Create the requests
$requests = function ($total) use($client) {
for ($i = 1; $i <= $total; $i++) {
yield new Request('GET', 'http://www.example.com/foo' . $i);
}
};
// Use the Pool::batch()
$pool_batch = Pool::batch($client, $requests(5));
foreach ($pool_batch as $pool => $res) {
if ($res instanceof RequestException) {
// Do sth
continue;
}
// Do sth
}