Pusher Undefined property: stdClass::$channels in Laravel Lumen - php

I'm making a POC with Lumen and Vue.JS. For now it just has to send a "hello world" message from the Lumen back-end to the Vue.JS front-end (which works). I have made an event which is triggered upon loading the page like this:
public function sendMessage(Request $request)
{
event(new MessageEvent('hello world'));
}
The MessageEvent looks like this (got this from the Pusher getting started help):
class MessageEvent extends Event implements ShouldBroadcast
{
/**
* Create a new event instance.
*
* #return void
*/
public $message;
public function __construct($message)
{
$this->message = $message;
}
public function broadcastOn()
{
return ['my-channel'];
}
public function broadcastAs()
{
return 'my-event';
}
}
This part is working, since I'm receiving this in the Vue.JS application:
Pusher : : ["Event recd",{"event":"my-event","channel":"my-channel","data":{"message":"hello world"}}]
Now comes the problem when i check the queue log. triggered with php artisan queue:listen, I see the following:
[2021-03-14 11:57:03][Bh7373O9EETAZc39M2RCSPmUTjwSbSmL] Processing: App\Events\MessageEvent
[2021-03-14 11:57:04][Bh7373O9EETAZc39M2RCSPmUTjwSbSmL] Failed: App\Events\MessageEvent
When I check the Lumen log files it says the following:
[2021-03-14 11:43:12] local.ERROR: Undefined property: stdClass::$channels {"exception":"[object] (ErrorException(code: 0): Undefined property: stdClass::$channels at /var/www/vendor/pusher/pusher-php-server/src/Pusher.php:538)
So I went ahead and checkt the Pusher.php file:
536: $result = json_decode($response['body']);
537:
538: if ($result->channels) {
539: $result->channels = get_object_vars($result->channels);
540: }
I decided to check what $response was, it gives the following:
[2021-03-14 11:57:04] local.INFO: array (
'body' => '{}',
'status' => 200,
)
Of course it can't get to $result->channels if response["body"]["channels"] doesn't exist.
When I go and check the Pusher API reference it says the following:
Which would mean that the body should indeed contain a JSON response. But when I scroll a bit further I see this:
Which should mean you don't have to set the info parameter, since it's optional and experimental.
[EXPERIMENTAL] If the info parameter is sent, then it returns a hash of unique channels that were triggered to. The hash maps from channel name to a hash of attributes for that channel (may be empty).
The response it expects with the info parameter set is this:
{
"channels": {
"presence-foobar": {
"user_count": 42,
"subscription_count": 51
},
"presence-another": {
"user_count": 123,
"subscription_count": 140
},
"another": {
"subscription_count": 13
}
}
}
Which is the channels object asked for.
My question is, did I miss something or is this a bug from Pusher? I'm really breaking a leg on this one so I hope someone can help me out.
Pusher API reference: https://pusher.com/docs/channels/library_auth_reference/rest-api

Fix composer.json
I have created an issue on the PHP package: https://github.com/pusher/pusher-http-php/issues/295
It is true this version is broken, but the fix should be in the composer.json file. Mine looked like this:
{
...
"require": {
...
"pusher/pusher-php-server": "5.0"
...
},
...
}
This means I specifically say that it should use version 5.0. But now I won't get version 5.2 for example, which means I won't get patches. According to the people who answered my issue on Github, I should change my composer.json and add a ^ in front of the version number so it does get the patches for that version. So it should be changed like this:
{
...
"require": {
...
"pusher/pusher-php-server": "^5.0"
...
},
...
}
Don't forget to run composer update afterwards.
According to Graham Campbell on Github:
Anyone who puts 5.0 in their composer.json file has made an error, since composer resolves this to the version 5.0.0.0 and not the version constraint ^5.0.
Fix pusher.php (not recommended)
Another workaround would be to directly edit /vendor/pusher/pusher-php-server/src/Pusher.php. Although not recommended, it does work.
536: $result = json_decode($response['body']);
537:
538: if ($result->channels) {
539: $result->channels = get_object_vars($result->channels);
540: }
This doesn't work since channels doesnt exist in the result object. It should check if the channels object exists first. You can do that by changing the above code to this:
536: $result = json_decode($response['body']);
537:
538: if (property_exists($result, 'channels')) {
539: $result->channels = get_object_vars($result->channels);
540: }

Related

Predis sub/pub on multiple channels with laravel

**Hi every one **,
I'm using predis with laravel in microservice architecture, and I have the following scenario :
App 1 :
in a controller I published an event in the 'timeslot' channel.
Redis::connection('timeslot')->publish('timeslot/updateStatus', json_encode([
'timeslot' => $this->timeSlot,
'customer' => $this->file->customer_id
]));
App2:
I subscribed to this channel 'timeslot' and I have a process that makes a modification in a model (that works).
I have a listener in this model when updating I launch another redis event to app 1 in other channel (event)
static::updated(function (TimeSlot $model) {
if($model->isDirty('state') && $model->state->value === 'booked'){
$result = json_encode(array_merge($model->toArray(), $model->agency->toArray()));
Redis::connection('event')->publish('timeslot/booked', $result);
}
});
when executing this part I would have the following error:
fclose(): supplied resource is not a valid stream resource
at vendor/predis/predis/src/Connection/StreamConnection.php:247
243▕ */
244▕ public function disconnect()
245▕ {
246▕ if ($this->isConnected()) {
➜ 247▕ fclose($this->getResource());
248▕ parent::disconnect();
249▕ }
250▕ }
251▕
Diagram
Any help please.
Thank you
I tried to do it on same channel but also i got an error

Is there a way to make Symfony controller return nothing?

I use Symfony 6.1 and PHP 8.1. I'm wondering if there is a way to make a Symfony controller return nothing. The why is I'm using Botman and unfortunalty it send itself a response to the client... So I need to make my controller to return nothing.
/**
* #param Request $request
*
* #return Response
*/
#[Route('/botman', methods: 'POST')]
public function test(Request $request): Response
{
DriverManager::loadDriver(WebDriver::class);
$adapter = new FilesystemAdapter();
$botman = BotManFactory::create([], new SymfonyCache($adapter), $request);
$botman->hears('hello', static function (BotMan $bot) {
$bot->reply('Yoooooo'); //<- Send a response to the client
});
$botman->fallback(static function (BotMan $bot) {
$bot->reply('try again'); //<- Send a response to the client
});
$botman->listen(); // Launch the maching callback
return new Response(); //Crash because a response have already been sent
}
This is what I get on my browser inspector.
{
"status": 200,
"messages": [
{
"type": "text",
"text": "try again",
"attachment": null,
"additionalParameters": []
}
]
}{
"type": "https:\/\/tools.ietf.org\/html\/rfc2616#section-10",
"title": "An error occurred",
"status": 500,
"detail": "Warning: Cannot modify header information - headers already sent by (output started at C:\\Users\\cimba\\Documents\\botman\\vendor\\symfony\\http-foundation\\Response.php:1265)",
"class": "ErrorException",
"trace": [...]
}
The only solution I have is to exit(); or die(); instead of the return but I it's not clean for production... Maybe there is a way to avoid BotMan to send it's response or to tell Symfony that I want my controller to return nothing (because botman does without symfony).
I have not used BotMan myself so this is a bit of a guess. You might try seeing if there a BotMan bundle because I suspect that if such a bundle exists then it would deal with this sort of thing as well as eliminating the need to use the static factory method.
However returning an empty response (which means no headers or content) might do the trick as well.
# src/Http/EmptyResponse.php
namespace App\Http;
use Symfony\Component\HttpFoundation\Response;
class EmptyResponse extends Response
{
public function send(): static
{
// Skip sending headers and content
// $this->sendHeaders();
// $this->sendContent();
// Keep some standard Symfony magic
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (\function_exists('litespeed_finish_request')) {
litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}
}
Then just return new EmptyResponse(); in your controller.
I tested this in a regular controller action and Symfony did not complain. But again I have no BotMan setup to test against. Be curious to see if it works.

Github Laravel workflow getting invalid JSON from routes

For the automated testing of a laravel API I am using the 'laravel' action on github actions, the one made by github actions.
The tests keep failing telling me invalid JSON returned from route, expected response code 200 but got 500, cannot read property status on null and cannot find in json
I'm using laravel sanctum. Could it be a csrf-token problem?
My action yml: https://gist.github.com/I2EJeffrey/77df8faac1b0f86623e2e4449f98d858
My response function:
* success response method.
*
* #return \Illuminate\Http\Response
*/
public function sendResponse($result, $message, $code = 200)
{
$response = [
'success' => true,
'data' => $result, // result is most often one or 2 arrays
'message' => $message,
];
return response()->json($response, 200);
}
Example test:
public function testSuccessfullyCreateAccommodationType()
{
$this->login(); // Login function that lots of tests need.
$response = $this->postJson('/api/v5/accommodations/1/types', ['accommodation_name'=>$this->createName()]);
$response
->assertJsonFragment(['success' => true])
->assertJsonStructure(['success', 'data' =>
[], 'message']); // The array is filled with keys
}
EDIT: 2 errors that I got by using withoutExceptionHandling: https://gist.github.com/I2EJeffrey/da23bfbdf5fba155456bd799a34f6276
EDIT 2: I also get the following warning: TTY mode requires /dev/tty to be read/writable.
EDIT 3: The client model and the client seeder. Whenever I run the tests a mysql docker container starts that gets a db migrated and seeded into it:
https://gist.github.com/I2EJeffrey/39c779df217c9a75a7569f6fa3957d77
https://gist.github.com/I2EJeffrey/41cbbdba8025d38547059d3a6f4d4392
My problem was that the DB didn't get seeded due to not having added $this->call(ClientSeeder::class); to the DatabaseSeeder. Which caused the routes to return null and thus wrong json.

Uncaught Error: Class "GuzzleHttp\Promise\EachPromise"

I tried implementing the below code but it is showing the EachPromise Error class not found and Promise Error class not found. Guzzle library is installed. Then also this error was there.
<?php
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Psr7\Response;
$users = ['one', 'two', 'three'];
$promises = (function () use ($users) {
foreach ($users as $user) {
// Using generator
yield $this->getAsync(
'https://api.demo.com/v1/users?username='
. $user);
}
})();
$eachPromise = new EachPromise($promises, [
// Number of concurrency
'concurrency' => 4,
'fulfilled' => function (Response $response) {
if ($response->getStatusCode() == 200) {
$user = json_decode(
$response->getBody(), true);
// processing response of the user
}
},
'rejected' => function ($reason) {
// handle promise rejected
}
]);
$eachPromise->promise()->wait();
?>
Seem there is no autoloader that locates and loads the class files mentioned in your code.
If your Guzzle package is installed with composer, you may try require_once() the /vendor/autoloader.php file in your source root.
Otherwise, try loading those class files individually - which is not recommended in modern PHP world(use autoloader whenever possible).
require_once("path-to/EachPromise.php");
require_once("path-to/Response.php");
EDIT
In the second sight, your code uses $this that requires an object instance context with $this->getAsync() being available, but apparently not so. The code seems like a partially copy-pasted code fragment from a Class object that won't run in your given set up.
You may need to set up the code context of your sample code.

PHP SDK not sending errors to Sentry when invoked from IBM Cloud Functions

I am using Serverless framework to deploy my PHP code as IBM Cloud Function.
Here is the code from the action PHP file:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
}
And this is the serverless.yml file:
service: cloudfunc
provider:
name: openwhisk
runtime: php
package:
individually: true
exclude:
- "**"
include:
- "vendor/**"
functions:
test-sentry:
handler: actions/test-sentry.main
annotations:
raw-http: true
events:
- http:
path: /test-sentry
method: post
resp: http
package:
include:
- actions/test-sentry.php
plugins:
- serverless-openwhisk
When I test the action handler from my local environment(NGINX/PHP Docker containers) the errors are being sent to Sentry.
But when I try to invoke the action from IBM Cloud nothing appears in the Sentry console.
Edit:
After some time trying to investigate the source of the problem I saw that its related with the async nature of sending the http request to Sentry(I have other libraries that make HTTP/TCP connections to Loggly, RabbitMQ, MySQL and they all work as expected):
vendor/sentry/sentry/src/Transport/HttpTransport.php
in the send method where the actual http request is being sent:
public function send(Event $event): ?string
{
$request = $this->requestFactory->createRequest(
'POST',
sprintf('/api/%d/store/', $this->config->getProjectId()),
['Content-Type' => 'application/json'],
JSON::encode($event)
);
$promise = $this->httpClient->sendAsyncRequest($request);
//The promise state here is "pending"
//This line here is being logged in the stdout of the invoked action
var_dump($promise->getState());
// This function is defined in-line so it doesn't show up for type-hinting
$cleanupPromiseCallback = function ($responseOrException) use ($promise) {
//The promise state here is "fulfilled"
//This line here is never logged in the stdout of the invoked action
//Like the execution never happens here
var_dump($promise->getState());
$index = array_search($promise, $this->pendingRequests, true);
if (false !== $index) {
unset($this->pendingRequests[$index]);
}
return $responseOrException;
};
$promise->then($cleanupPromiseCallback, $cleanupPromiseCallback);
$this->pendingRequests[] = $promise;
return $event->getId();
}
The requests that are registered asynchronously are sent in the destructor of the HttpTransport instance or when PHP shuts down as a shutdown function is registered. In OpenWhisk we never shut down as we run in a never-ending loop until the Docker container is killed.
Update: You can now call $client-flush() and don't need to worry about reflection.
main() now looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$client->flush();
return [
'body' => ['result' => 'ok']
];
}
Original explanation:
As a result, to make this work, we need to call the destructor of the $transport property of the Hub's $client. Unfortunately, this private, so the easiest way to do this is to use reflection to make it visible and then call it:
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
This will make the $transport property visible so that we can retrieve it and call its destructor which will in turn call cleanupPendingRequests() that will then send the requests to sentry.io.
The main() therefore looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
return [
'body' => ['result' => 'ok']
];
}
Incidentally, I wonder if this Sentry SDK works with Swoole?
Function runtimes are "paused" between requests by the platform. This means any background processes will be blocked if they aren't finished when the function returns.
It looks like the asynchronous HTTP request doesn't get a chance to complete before the runtime pauses.
You will need to find some way to block returning from the function until that request is completed. If the Sentry SDK has some callback handler or other mechanism to be notified when messages have been sent, you could use that?

Categories