Predis sub/pub on multiple channels with laravel - php

**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

Related

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.

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

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: }

Laravel Broadcasting, how to auth to a presence channel?

I'm trying to join a presence channel (Public channels work well), but I can't get this to work:
Vue code:
mounted(){
Echo.join('game.' + "0").here((users) => {
alert("In the channel!");
})
.joining((user) => {
console.log("Someone entered");
})
.leaving((user) => {
console.log(user.name);
})
.listen('GameEvent', (e) => {
console.log("Hey")
});
Echo.channel('NewSentence')
.listen('NewSentence',(sentence)=>{
alert("HOLA");
});
}
I'm trying to join the channel "game.0". As I'm using Laravel Passport I need to authenticate myself with a token, and that is working. Sending the auth request for Laravel Echo returns a key, but the JavaScript events are not triggering .here(), .listening() ....
BroadcastService provider boot function:
public function boot() {
Broadcast::routes(["middleware" => "auth:api"]);
require base_path('routes/channels.php');
}
channels.php
Broadcast::channel('game.0', function ($user,$id) {
return ['id' => $user->id];
});
The auth route:
Route::post('/broadcasting/auth', function(Request $request){
$pusher = new Pusher\Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
array(
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
)
);
return $pusher->socket_auth($request->request->get('channel_name'),$request->request->get('socket_id'));
});
Do I need to do something extra to make it work? This is the auth request:
EDIT:
GameEvent event:
class GameEvent implements ShouldBroadcastNow {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $gameEvent;
public $gameId;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($gameEvent, $gameId) {
//
$this->gameEvent = $gameEvent;
$this->gameId = $gameId;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\PresenceChannel|array
*/
public function broadcastOn() {
//return new PrivateChannel('channel-name');
return new PresenceChannel('game.0');
}
public function broadcastWith() {
return $this->gameEvent;
}
}
EDIT:
I've hardcoded the names: 'game.0' is now hardcoded in the routes/channels.php route, in the Echo connection and in the GameEvent. I also removed broadcastAs(). After entering the laravel-websockets debugging dashboard I found that the channel I want to subscribe doesn't even appear. It looks like it won't start a connection, but I can't figure out what it going on.
I hardcoded the
The problem here seems to be that the Echo is not listening on proper channel. First of all the Echo.join is using channel game.0 in which 0 is a user's id, and i don't think that there is actually a user with id 0. Secondly, you are broadcasting as
GameEvent
and Echo is connecting to channel named game.{id} I suggest that you either remove the broadcastAs() function from your event file or listen on GameEvent. Also use the websocket dashboard for testing this. The dashboard will be available at
/laravel-websockets
route automatically, which is available only for local environment so make sure that environment is local in your .env.
Use the debugging dashboard provided by laravel-websockets to send data to channels, first connect to your web socket within the dashboard then just enter the channel name, event name and data in JSON format and hit send on the dashboard.
Try finding out if that helps with resolving your problem.
I also recommend thoroughly reading laravel's official documentation on broadcasting as well as laravel-websockets debugging dashboard guide.
Also update what you got in result to this question.

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?

Running a cron job in Symfony2 - trying to call a method in a controller

I'm having a nightmare trying to set up a cron job in my Symfony2 project.
I understand the principle of setting it up and where to put the code but I just cannot get it to do what I need.
Basically, I need the cron job to run every day and check a database of clients in order to find out if an invoice needs sending. The actual client referencing is yet to be done but I have written a test which I want to generate and email and invoice based on hardcoded values I pass to the function.
// AppBundle/Command/CronRunCommand.php
protected function execute(InputInterface $input, OutputInterface $output)
{
$request = new Request();
$request->attributes->set('client','14');
$request->attributes->set('invoice_id','3');
$request->attributes->set('dl','0');
$output->writeln('<comment>Running Invoice Cron Task...</comment>');
return $this->getContainer()->get('invoices')->generateInvoiceAction($request);
}
I have set invoices up as a service in my config.yml:
services:
invoices:
class: AppBundle\Controller\InvoiceController
And in InvoiceController there is a function that will generate an invoice by using Invoice Painter Bundle and then send it to the specified email address (currently hard coded for development purposes).
When I run the cron command on my console, it throws the following error:
[Symfony\Component\Debug\Exception\FatalErrorException]
Error: Call to a member function has() on null
I have searched for this and I believe it is to do with the fact that it's referencing a controller method and my command file does not extend controller, but I'm so confused about how I can do this - surely there is a way of running a method in a controller as a cron job?
Any help appreciated.
Michael
I fear you may still not be understanding the big picture. Console apps don't have a request object and thus the whole request_stack is not going to work. I know you tried creating a request object but that is not going to impact the request stack.
Your console app should look something like:
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = [
'client' => 14,
'invoice' => 3,
'dl' => 0,
];
$invoiceManager = $this->getContainer()->get('invoices');
$results = $invoiceManager->generateInvoice($data);
}
Your controller action would be something like:
public function generateInvoiceAction(Request $request)
{
$data = [
'client' => $request->attribute->get('client'),
'invoice' => $request->attribute->get('invoice'),
'dl' => $request->attribute->get('dl'),
];
$invoiceManager = $this->getContainer()->get('invoices');
$results = $invoiceManager->generateInvoice($data);
The invoice manager might look like:
class InvoiceManager {
public function __construct($em) {
$em = $this->em;
}
public function generateInvoice($data) {
$client = $this->em->find('Client',$data['client']);

Categories