Slim 2 to Slim 3 method error - php

I am trying to convert a slim 2 project to slim 3.
I am stucking with a problem.
When I call my method:
http://localhost:8000/task_manager/v1/tasks
I get the next error:
Catchable fatal error: Argument 1 passed to authenticate() must
be an instance of Slim\Route, instance of
Slim\Http\Request given in
C:\wamp64\www\task_manager\v1\index.php on line 42.
How could I fix it? Some help will be appreciated.
Here is my php code:
$app = new \Slim\App();
...
...
function authenticate(\Slim\Route $route) {
// Getting request headers
$headers = apache_request_headers();
$data = array();
$app = AppContainer::getInstance();
// Verifying Authorization Header
if (isset($headers['Authorization'])) {
$db = new DbHandler();
// get the api key
$api_key = $headers['Authorization'];
// validating api key
if (!$db->isValidApiKey($api_key)) {
// api key is not present in users table
$data["error"] = true;
$data["message"] = "Access Denied. Invalid Api key";
return echoRespnse(401, $data, $response);
//$app->stop();
return $response->withStatus(401)->withBody("Bad Request");
} else {
global $user_id;
// get user primary key id
$user_id = $db->getUserId($api_key);
}
} else {
// api key is missing in header
$data["error"] = true;
$data["message"] = "Api key is misssing";
return echoRespnse(400, $data, $response);
}
}
$app->get('/tasks', function (Request $request, Response $response) {
global $user_id;
$data = array();
$db = new DbHandler();
// fetching all user tasks
$result = $db->getAllUserTasks($user_id);
$data["error"] = false;
$data["tasks"] = array();
// looping through result and preparing tasks array
while ($task = $result->fetch_assoc()) {
$tmp = array();
$tmp["id"] = $task["id"];
$tmp["task"] = $task["task"];
$tmp["status"] = $task["status"];
$tmp["createdAt"] = $task["created_at"];
array_push($data["tasks"], $tmp);
}
return echoRespnse(200, $data, $response);
})->add('authenticate');

In Slim2 you could add middleware like this:
<?php
$aBitOfInfo = function (\Slim\Route $route) {
echo "Current route is " . $route->getName();
};
$app->get('/foo', $aBitOfInfo, function () {
echo "foo";
});
This is not possible in slim3: first the middleware must be added with the add method like this:
$app->get('/foo', function () {
echo "foo";
})->add($aBitOfInfo);
Secondly middleware doesn't have the \Slim\Route-Object as first parameter.
These are not the Request, Response and the callable of the next middleware. So this should be like this:
/**
* Example middleware closure
*
* #param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
* #param \Psr\Http\Message\ResponseInterface $response PSR7 response
* #param callable $next Next middleware
*
* #return \Psr\Http\Message\ResponseInterface
*/
$aBitOfInfo = function ($request, $response, $next) {
$response->getBody()->write('BEFORE');
$response = $next($request, $response);
$response->getBody()->write('AFTER');
return $response;
};
(Source https://www.slimframework.com/docs/concepts/middleware.html)

Related

Laravel Pusher array_merge: Expected parameter 2 to be an array, null given

i'm following a tutorial from pusher to display notification on the website. Everything has been in line with the tutorial, however this particular error showed up when i try to access the notification on localhost:8000/test i have no clue on how to fix it.
the error message
expected result : notification send message
output : array_merge() error
related tutorial : https://pusher.com/tutorials/web-notifications-laravel-pusher-channels
related file : C:\xampp\htdocs\inventory-prototype\vendor\pusher\pusher-php-server\src\Pusher.php:518
here's my Events/ItemAdd :
class ItemAdd implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($user)
{
$this->user = $user;
$this->message = '{ $user } added an item';
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return ['item-add'];
}
}
here's my web.php:
Route::get('test', function () {
dd(event(new App\Events\ItemAdd('Someone')));
return "Event has been sent!";
});
vendor/pusher/src/Pusher.php -> Trigger
/**
* Trigger an event by providing event name and payload.
* Optionally provide a socket ID to exclude a client (most likely the sender).
*
* #param array|string $channels A channel name or an array of channel names to publish the event on.
* #param string $event
* #param mixed $data Event data
* #param array $params [optional]
* #param bool $already_encoded [optional]
*
* #throws PusherException Throws PusherException if $channels is an array of size 101 or above or $socket_id is invalid
* #throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
*
* #return object
*/
public function trigger($channels, $event, $data, $params = array(), $already_encoded = false)
{
if (is_string($channels) === true) {
$channels = array($channels);
}
$this->validate_channels($channels);
if (isset($params['socket_id'])) {
$this->validate_socket_id($params['socket_id']);
}
$has_encrypted_channel = false;
foreach ($channels as $chan) {
if (PusherCrypto::is_encrypted_channel($chan)) {
$has_encrypted_channel = true;
}
}
if ($has_encrypted_channel) {
if (count($channels) > 1) {
// For rationale, see limitations of end-to-end encryption in the README
throw new PusherException('You cannot trigger to multiple channels when using encrypted channels');
} else {
$data_encoded = $this->crypto->encrypt_payload($channels[0], $already_encoded ? $data : json_encode($data));
}
} else {
$data_encoded = $already_encoded ? $data : json_encode($data);
}
$query_params = array();
$path = $this->settings['base_path'].'/events';
// json_encode might return false on failure
if (!$data_encoded) {
$this->log('Failed to perform json_encode on the the provided data: {error}', array(
'error' => print_r($data, true),
), LogLevel::ERROR);
}
$post_params = array();
$post_params['name'] = $event;
$post_params['data'] = $data_encoded;
$post_params['channels'] = array_values($channels);
$all_params = array_merge($post_params, $params);
$post_value = json_encode($all_params);
$query_params['body_md5'] = md5($post_value);
$ch = $this->create_curl($this->channels_url_prefix(), $path, 'POST', $query_params);
$this->log('trigger POST: {post_value}', compact('post_value'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
$response = $this->exec_curl($ch);
if ($response['status'] !== 200) {
throw new ApiErrorException($response['body'], $response['status']);
}
$result = json_decode($response['body']);
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
}
any help will be appreciated
i say whatever, i just downgraded to pusher 4.1, on composer.json look for pusher and change the version to 4.1 in case anybody on earth other than me get the same error.
This error was resolved in the pusher-http-php library v5.0.1 and Laravel v8.29.0. https://github.com/pusher/pusher-http-php/issues/288
You can find the solution to this problem in this comment by ben-pusher to issue error array_merge - laravel 8 - php74 #278:
You may need to use composer require pusher/pusher-php-server ^4.1- support for v5.0.0 of this library hasn't been added to Laravel yet.

"start_batch was called incorrectly" on gRPC unary call

I make an unary call from PHP code like that:
public function getByIDs(array $ids): array
{
$request = new GetByIDsRequest();
$request->setIDs($ids);
$grpcRequest = $this->client->GetByIDs($request, [], ['timeout' => $this->waitTimeout]);
$response = $this->callWithRetries($grpcRequest);
if (!$response->isOk()) {
$status = $response->getStatus();
throw new GrpcServerException(__FUNCTION__, $status->getDetails(), (int)$status->getCode());
}
... // business logic here
}
private function callWithRetries(\Grpc\UnaryCall $grpcRequest): GrpcUnaryCallResponse
{
$response = null;
for ($attempt = 0; $attempt <= self::$maxRetries; $attempt++) {
if ($attempt > 0) {
usleep(mt_rand(1, 5) * self::$waitBeforeRetry);
}
$response = $this->unaryCall($grpcRequest);
$status = $response->getStatus()->getCode();
if ($response->isOk() || !self::isRetryable($status)) {
return $response;
}
}
return $response;
}
protected function unaryCall(UnaryCall $call): GrpcUnaryCallResponse
{
[$response, $status] = $call->wait();
return new GrpcUnaryCallResponse(
$response,
new GrpcStatus($status)
);
}
Sometimes I get the LogicException with message "start_batch was called incorrectly". Any thoughts how to deal with that exception and what is the root cause?
After some investigation I found out that the problem occurs because of reusing the instance of \Grpc\UnaryCall in retries. You must not do that otherwise you get a core dump.
The reason we got the LogicException but not a core dump is using a patched gRPC extension.
Some more technical details you can find in this issue

Slim query parameter

I want to add a query parameter in my GET Route, which is this one:
$app->get('/rooms', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
try {
$room = new \Riecken\PBS\controller\RoomController();
$result = $room->getRoomsWithDetails();
$response = $response->withJson($result);
$response = $response->withStatus(200);
return $response;
}catch(Exception $e) {
$response->getBody()->write($e->getMessage());
return $response->withStatus($e->getCode());
}
});
What I want to do is that I only want to execute this function when I type "expandAll".
I googled it and I could find something in the Slim documentation:
https://www.slimframework.com/docs/v3/objects/request.html
But I dont know how to implement it.
So in my case:
If "expandAll" I want to execute the function you see above (getRoomWithDetails(), else I want to execute another function.
Is that possible?
Thanky you very much!
You could just pass the required query parameters to getRoomsWithDetails or you just add a if condition.
Example
$app->get('/rooms', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
try {
$expandAll = $request->getParam('expandAll');
$room = new \Riecken\PBS\controller\RoomController();
if ($expandAll) {
$result = $room->getRoomsWithDetails();
} else {
$result = $room->anotherMethod();
}
$response = $response->withJson($result);
$response = $response->withStatus(200);
return $response;
} catch(Exception $e) {
$response = $response->withJson(['error' => ['message' => $e->getMessage()]]);
return $response->withStatus(500);
}
});

Using GuzzleHttp/Psr7/Response Correctly

Not sure what is the correct way to display in a php page a Psr7 Guzzle Response.
Right now, I am doing:
use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\Response;
class Main extends \pla\igg\Main
{
function __construct()
{
$stream = new BufferStream();
$stream->write("Hello I am a buffer");
$response = new Response();
$response = $response->withBody($stream);
$response = $response->withStatus('201');
$response = $response->withHeader("Content-type", "text/plain");
$response = $response->withAddedHeader("IGG", "0.4.0");
//Outputing the response
http_response_code($response->getStatusCode());
foreach ($response->getHeaders() as $strName => $arrValue)
{
foreach ($arrValue as $strValue)
{
header("{$strName}:{$strValue}");
}
}
echo $response->getBody()->getContents();
}
}
Is there a more OOP way to display the response?
A more OOP way of doing the same thing is to create a Sender object that requires a ResponseInterface in its constructor. This class would be responsible for setting headers, clearing buffers and render the response:
use Psr\Http\Message\ResponseInterface;
class Sender
{
protected $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
public function send(): void
{
$this->sendHeaders();
$this->sendContent();
$this->clearBuffers();
}
protected function sendHeaders(): void
{
$response = $this->response;
$headers = $response->getHeaders();
$version = $response->getProtocolVersion();
$status = $response->getStatusCode();
$reason = $response->getReasonPhrase();
$httpString = sprintf('HTTP/%s %s %s', $version, $status, $reason);
// custom headers
foreach ($headers as $key => $values) {
foreach ($values as $value) {
header($key.': '.$value, false);
}
}
// status
header($httpString, true, $status);
}
protected function sendContent()
{
echo (string) $this->response->getBody();
}
protected function clearBuffers()
{
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (PHP_SAPI !== 'cli') {
$this->closeOutputBuffers();
}
}
private function closeOutputBuffers()
{
if (ob_get_level()) {
ob_end_flush();
}
}
}
Use it like this:
$sender = new Sender($response);
$sender->send();
Better yet, you could inject the Sender into your app object and transform it in a class variable, so you'd call it like this:
function renderAllMyCoolStuff()
{
$this->sender->send();
}
I'll leave it as a reader's exercise to implement getters and setters for the Response object, plus a method to receive some content string and transform it into a Response object internally.
Guzzle is a library for doing HTTP calls inside your app, it has nothing to do with the end user communication.
If you need to send specific headers to your end user, just use http_response_code() (that you are already using), header() and echo. Or see the docs for your framework, if you use one (Symfony, Slim, whatever).

Slim Framework - How to query db in normal function

I'm very new to PHP and Slim Framework which helps creating APIs.
Everything is ok If i query db inside $app->post or get. But I want to separate it to normal function. It will help when I need to use it later in other APIs.
I tried to call this
$app->get('/search/[{phone}]', function($request, $response, $args) use ($app){
$token = $response->getHeader('token');
// $phone = $args['phone'];
if (isTokenValid($token)){
return $this->response->withJson("valid");
}
return $this->response->withJson("invalid");
});
My isTokenValid() function
function isTokenValid($token){
$sql = 'SELECT id FROM users WHERE token = :token';
$s = $app->db->prepare($sql); //<< this line 25
$s->bindParam(':token', $token);
if ($s->execute()){
if($sth->rowCount() > 0){
return true;
}
}
return false;
}
But I get 500 Internal Server Error
Type: Error
Message: Call to a member function prepare() on null
File: /Applications/MAMP/htdocs/aigoido/src/functions.php
Line: 25
How to call it outside $app? Thanks.
You want to create a dependency injection container for your database connection and pass that object in as the function parameter rather than app object. This makes the db connection reusable throughout your app.
https://www.slimframework.com/docs/concepts/di.html
Also, you can return $response rather than $this->response.
$c = $app->getContainer();
$c['db'] = function() {
return new DB($host,$user,$pass,$name);
};
$app->post('/search/[{phone}]', function($request, $response, $args) use ($c) {
$token = $response->getHeader('token');
// $phone = $args['phone'];
if (isTokenValid($c->db,$token)){
return $response->withJson("valid");
}
return $response->withJson("invalid");
});
function isTokenValid($db, $token){
$sql = 'SELECT id FROM users WHERE token = :token';
$s = $db->prepare($sql);
$s->bindParam(':token', $token);
if ($s->execute()){
if($sth->rowCount() > 0){
return true;
}
}
return false;
}
Pass $app to your function as parameter. The function has it own context so $app is not available without that.
function isTokenValid($token, $app){
$sql = 'SELECT id FROM users WHERE token = :token';
$s = $app->db->prepare($sql); //<< this line 25
$s->bindParam(':token', $token);
if ($s->execute()){
if($sth->rowCount() > 0){
return true;
}
}
return false;
}

Categories