Laravel Websocket package issue with the latest Laravel, pusher, and Echo package - php

I am working on Laravel 9 where I install Laravel WebSocket, Laravel Echo, and Pusher PHP server.
By the way, I didn't use the official Pusher application, just using the package as per Laravel-WebSocket package documentation suggested.
User case - I want to update the site model value and send a notification (broadcast and mail) to the end user as soon as the user deletes a site.
Everything is installed and working fine but I found some glitches in the Laravel-WebSocket, Pusher package.
I have created the following event which will broadcast to the end user.
SiteDelete.php
<?php
namespace App\Events;
use App\Models\Site;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SiteDeleted implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* The site instance.
*
* #var \App\Models\Site
*/
public $site;
/**
* The name of the queue connection to use when broadcasting the event.
*
* #var string
*/
public $connection = 'database';
/**
* The name of the queue on which to place the broadcasting job.
*
* #var string
*/
public $queue = 'default';
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Site $site)
{
$this->site = $site;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
// return new PrivateChannel('site.delete.'.$this->site->id); // this is not working.
// return [new PrivateChannel('site.delete.'.$this->site->id)]; // this is not working.
return [new PrivateChannel('site.delete.'.$this->site->id), new Channel('mock')]; // This will call but I need to pass two channels intentionally.
}
/**
* Get the data to broadcast.
*
* #return array
*/
public function broadcastWith()
{
return ['id' => $this->site->id];
}
}
app.js
Echo.private("site.delete.1")
.listen('SiteDeleted', (e) => {
console.log("SiteDeleted");
console.log(JSON.stringify(e));
})
Echo.private('App.Models.User.7')
.notification((notification) => {
console.log("App.Models.User");
console.log(notification);
});
Problem
As you can see comments in my event class's broadcastOn method where I need to pass two channels. One is real and the second one is fake. So ultimately you need to pass at least two channels so the pusher request will have a channels parameter [which will work] but the channel parameter never works[i.e when you pass a single channel].
I can able to send custom events from the WebSocket GUI. i.e from http://localhost:8000/laravel-websockets URL. but those events are never caught by the front end unless I do it the dirty way.
The notifications are never caught by the front end due to this channel and channels parameter issue.
Dirty Way[Yes I know we should never touch the vendor folder but just curious to know why the things are not working]
I checked the vendor folder very deeply and I come to know, in the vendor/pusher/pusher-php-server/src/Pusher.php under the make_event function if I update the following line then it starts working without passing two channels.
vendor/pusher/pusher-php-server/src/Pusher.php
private function make_event(array $channels, string $event, $data, array $params = [], ?string $info = null, bool $already_encoded = false): array
{
// if (count($channel_values) == 1) {
// $post_params['channel'] = $channel_values[0];
// } else {
// $post_params['channels'] = $channel_values;
// }
$post_params['channels'] = $channel_values;
}
My Research
As the WebSocket package suggests installing pusher-php-server version 3.0 but I install the latest one i.e 7. Version 3.0 is incompatible with Laravel 9. But I can't and don't want to install the older version.
I think the WebSocket package is not able to send the event and data on a single channel with a newer version of pusher-php-server.
I can't raise an issue (or blame it) for Pusher SDK because we are just replacing the package and I think the Pusher SDK package is working fine when you use their credentials(ie. you have to create an app on Pusher).
Even if you can check on the WebSocket dashboard i.e http://localhost:8000/laravel-websockets when you send the event it will never catch in the front end. But as soon as you update the Pusher.php file it starts catching an event on the front end.
due to the above reason, as you know the notification are sent to the user on their private channels, So I can't add a mock channel for notification as I did for my event, so notification will never catch by the frontend application.
composer.json
"beyondcode/laravel-websockets": "^1.13",
"pusher/pusher-php-server": "^7.2",
"laravel/framework": "^9.19",
package.json
"pusher-js": "^7.5.0",
"laravel-echo": "^1.14.2",
I tried the explicit way as well i.e using the pusher SDK's functions[which are giving 200 status code] but not working. As soon as I do it the dirty way it starts working, I mean everything starts working without any issue.
public function pusherTesting(Request $request)
{
$path = "/apps/123456/events";
$settings = [
'scheme' => 'http',
'port' => '6001',
'path' => '',
'timeout' => '30',
'auth_key' => '1b5d6e5b1ab73b',
'secret' => '3739db6a99c1ba',
'app_id' => '123456',
'base_path' => '/apps/123456',
'host' => '127.0.0.1',
];
$params = [];
$body = '{"name":"Illuminate\\Notifications\\Events\\BroadcastNotificationCreated","data":"{\"site_id\":1,\"domain_url\":\"yucentipede-tuvo.blr3.instawp-testing.xyz\",\"save\":\"socket\",\"id\":\"2f53aac0-8d83-45f4-962d-516c1c8bc97c\",\"type\":\"App\\\\Notifications\\\\SiteDeletedNotification\"}","channels":["private-App.Models.User.7"]}';
$params['body_md5'] = md5($body);
$params_with_signature = \Pusher\Pusher::build_auth_query_params(
$settings['auth_key'],
$settings['secret'],
'POST',
$path,
$params
);
$headers = [
'Content-Type' => 'application/json',
'X-Pusher-Library' => 'pusher-http-php 7.2.1'
];
$client = new \GuzzleHttp\Client();
try {
$response = $client->post(ltrim($path, '/'), [
'query' => $params_with_signature,
'body' => $body,
'http_errors' => false,
'headers' => $headers,
'base_uri' => 'http://127.0.0.1:6001'
]);
} catch (Exception $e) {
print_r($e->getMessage());
}
$response_body = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
echo $status = $response->getStatusCode();
die;
}

Related

How to show an alert message to a user with Event Listener

I have created an Event called UserWalletNewTransaction.php and added this to it:
public $transaction;
public function __construct($transaction) {
$this->$transaction = $transaction;
}
Now in order to fire this event at the Controller, I coded this:
$newTransaction = UserWalletTransaction::create(['user_id' => $user_id, 'wallet_id' => $wallet_id, 'creator_id' => $creator_id, 'amount' => $amount_add_value, 'description' => $trans_desc]);
event(new UserWalletNewTransaction($newTransaction));
Then at the Listener, UserWalletNotification.php, I tried:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
}
So the scenario is, when Admins create a new Transaction for a custom user, a new alert message must be sent for him/her to let him/her know that new transaction was added for him/her.
But I don't really know how to do that.. So if you know, please let me know, I would really appreciate that...
Thanks in advance.
If by alert you mean showing a message on the web interface, use flash data.
https://laravel.com/docs/5.8/session#flash-data
$newTransaction = UserWalletTransaction::create(...);
event(new UserWalletNewTransaction($newTransaction));
$request->session()->flash('status', 'Transaction done.');
return view(...)
<span>{{ session('status') }}</span>
If you mean sending an email, just use the Mail facade in your listener to send a mailable.
https://laravel.com/docs/5.8/mail#sending-mail
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
Mail::to($user)->send(new TransactionDoneMail($event->transaction)); // TransactionDoneMail being your mailable class, made with "php artisan make:email TransactionDoneMail"
}
There are nice examples on how to build a mailable class in the documentation.
https://laravel.com/docs/5.8/mail#writing-mailables
There are many different things you can do in terms of "alerting" the customer.
One route would be to send an email or text message in your event listener. See https://laravel.com/docs/5.8/mail for help doing it via email.
Another way would be using browser push notifications. You could use OneSignal for this. You would setup the front end to display an alert to a customer user asking if they would like to subscribe to push notifications. When they subscribe, you will get back an ID for that specific user. Make an API call to your Laravel app, and store that ID in the users table (you will need a migration). Then from within your event listener, you can make a call to OneSignal's API and send the user a notification, which will popup on their computer.
Here is an example of using OneSignal to send an event to a user via the API:
Your OneSignal service:
<?php
namespace App\Services;
use App\User;
use GuzzleHttp\Client;
class OneSignalService
{
public function sendNotificationToUser(User $user, string $title, string $message, string $url, string $subtitle = null)
{
if (!$user->one_signal_id) {
return;
}
$fields = [
'app_id' => config('services.onesignal.app_id'),
'include_player_ids' => [$user->one_signal_id],
'headings' => ['en' => $title],
'contents' => ['en' => $message],
'url' => $url,
];
if ($subtitle) {
$fields['subtitle'] = ['en' => $subtitle];
}
$client = new Client([
'base_uri' => 'https://onesignal.com/api/v1/',
'headers' => [
'Content-Type' => 'application/json; charset=utf-8',
'Authorization' => 'Basic <<API_KEY>>',
]
]);
$client->request('POST', 'notifications', [
'json' => $fields
])
}
}
UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
$oneSignal = new OneSignalService();
$oneSignal->sendNotificationToUser($user, 'New Transaction', 'You have a new transaction', 'yourwebsite.com');
}
The way I would go about this would be via broadcasting, which would use websockets to instantly send the customer user an alert to their browser, in which you could then display a popup of some sort. You could install Laravel Echo Server, but to keep things simple you can use Pusher. Follow the guide to install on the front end of your website.
Then, create a private channel specific to a customer user "transaction.created.{{USER ID}}" and listen for it on your front end.
Within Laravel you will install the PHP Pusher SDK via composer.
Then within your .env file set:
BROADCAST_DRIVER=pusher
Next, open up channels.php within your routes directory in Laravel and add:
Broadcast::channel('transaction.created.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
This will verify authentication for your user to the private channel.
Create an Laravel Event:
<?php
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TransactionCreated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user = null;
public $transaction = null;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $user, UserWalletTransaction $transaction)
{
$this->user = $user;
$this->transaction = $transaction;
}
public function broadcastWith(): array
{
return $this->transaction->toArray(); //Or whatever information you want to send to the front end
}
public function broadcastAs(): string
{
return 'TransactionCreated';
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('transaction.created.' . $this->user->id);
}
}
Fire the event from UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
event(new TransactionCreated($user, $event->transaction));
}
Lastly, create some sort of popup and display it on the front end when your callback function for the private channel is hit.
If you need anymore help, feel free to comment.
What you want to do I believe, is asynchronous notifications.
Well, if you really mean flash messages - those who are stored in session - it will not be so easy.
Normal steps are create flash message for the user currently logged in on a website, stored in session that is unique for the current user. It can be shown only for this user.
What you want is to create flash message as the admin (from admin perspective) , then only to admin it can be shown.
I would do this, create new table, when these notification messages will be stored. Some table with columns like id, user_id, message, type, created_date, shown_date. Admins will put alert/notification messages for each user. Then create class (can be in controller for example) that will check this table for each user and if there is new not already shown message, show it normally in flash message for that current user. Dont forget to mark that message as shown. That is it.
So much for custom solution. I belive there must be some for example jQuery/other Jvascript plugins or Laravel plugins for asynchronous notifications, please check those.

Using TypoScriptFrontendController features in AuthenticationService? / Save Data to User in Authenticator?

So I use a Service Class (extends from TYPO3\CMS\Core\Authentication\AuthenticationService) to authenticate our Frontend Users using OAuth2. These Services are automatically instantiated and called via Typos own Middleware: FrontendUserAuthenticator.
In this class I used to save data from the authentication result to $GLOBALS['TSFE']->fe_user using setKey('ses', 'key', 'data'), which seems is not possible anymore since v10. How would I go about still doing this?
The documentation is sparse
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Deprecation-85878-EidUtilityAndVariousTSFEMethods.html
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Context/Index.html
I've tried the following:
constructor injecting the TSFE using DI
class FrontendOAuthService extends AuthenticationService
{
public function __construct(TypoScriptFrontendController $TSFE) {
=> LogicException: TypoScriptFrontendController was tried to be injected before initial creation
changing the Middlewares order to have it instantiate before the Auth Middleware
(packages/extension_name/Configuration/RequestMiddlewares.php)
return [
'frontend' => [
'typo3/cms-frontend/tsfe' => [
'disabled' => true,
],
'vendor/extension_name/frontend-oauth' => [
'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
'before' => [
'typo3/cms-frontend/authentication',
],
'after' => [
'typo3/cms-frontend/eid',
'typo3/cms-frontend/page-argument-validator',
],
],
],
];
=> UnexpectedValueException: Your dependencies have cycles. That will not work out.
instantiating the TSFE myself
/** #var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var DealerService $dealerService */
$lang = $site->getDefaultLanguage();
$siteLanguage = $objectManager->get(SiteLanguage::class, $lang->getLanguageId(), $lang->getLocale(), $lang->getBase(), []);
/** #var TypoScriptFrontendController $TSFE */
$TSFE = $objectManager->get(
TypoScriptFrontendController::class,
GeneralUtility::makeInstance(Context::class),
$site,
$siteLanguage,
GeneralUtility::_GP('no_cache'),
GeneralUtility::_GP('cHash')
);
=> the $TSFE->fe_user is an emptystring ("")
using the UserAspect
/** #var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
$feUser = $context->getAspect('frontend.user');
$feUser->set...
=> Aspects are read-only
adding vars to the user data in the getUser method of the AuthenticationService
(packages/extension_name/Classes/Service/FrontendOAuthService.php)
public function getUser()
{
$user = allBusinessCodeHere();
$user['my_own_key'] = 'myData';
return $user;
=> is not propagated to the UserAspect(frontend.user) nor the $TSFE->fe_user
I'm out of ideas guys.
I had a similar problem when i wanted to use redirects with record links.
I ended up disabling the original redirect middleware and adding my own with a mocked version of tsfe.
The extension can be found here:
https://github.com/BenjaminBeck/bdm_middleware_redirect_with_tsfe
Late to the party, but I had the same issue and was able to solve it:
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.html states:
Storing session data from a Frontend User Session / Anonymous session
is now triggered within the Frontend User
(frontend-user-authenticator) Middleware, at a later point - once the
page was generated. Up until TYPO3 v9, this was part of the
RequestHandler logic right after content was put together. This was
due to legacy reasons of the previous hook execution order. Migration
Consider using a PSR-15 middleware instead of using a hook, or
explicitly call storeSessionData() within the PHP hook if necessary.
In my MyAuthenticationService extends AbstractAuthenticationService in method getUser() I set $_SESSION['myvendor/myextension/accessToken'] to the token received by the external oauth service. In my SaveSessionMiddleware I save this token to the FrontendUserAuthentication object using setKey() which by then is available:
EXT:myextension/Configuration/RequestMiddlewares.php
return [
'frontend' => [
'myvendor/myextension/save-session-middleware' => [
'target' => \MyVendor\MyExtension\Middleware\SaveSessionMiddleware::class,
'after' => [
'typo3/cms-frontend/authentication',
],
]
]
];
EXT:myextension/Classes/Middleware/SaveSessionMiddleware.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class SaveSessionMiddleware implements MiddlewareInterface {
/**
* #param ServerRequestInterface $request
* #param RequestHandlerInterface $handler
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!empty($_SESSION['myvendor/myextension/accessToken'])) {
$this->getFrontendUserAuthentication()->setKey(
'ses',
'myvendor/myextension/accessToken',
$_SESSION['myvendor/myextension/accessToken']);
unset($_SESSION['myvendor/myextension/accessToken']);
}
return $handler->handle($request);
}
private function getFrontendUserAuthentication(): FrontendUserAuthentication {
return $GLOBALS['TSFE']->fe_user;
}
}

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.

Laravel: Actively listening events from external service

I'm developing a PBX monitoring app with Laravel. On PBX side, Asterisk is managing the calls.
I can use PAMI client to receive all events from Asterisk Manager Interface. If I simply run this script from console, all the events on Asterisk are printed on the screen in real time:
<?php
require __DIR__ . '/vendor/autoload.php';
use PAMI\Client\Impl\ClientImpl as PamiClient;
use PAMI\Message\Event\EventMessage;
use PAMI\Listener\IEventListener;
$pamiClientOptions = array(
'host' => '',
'scheme' => 'tcp://',
'port' => 5038,
'username' => '',
'secret' => '',
'connect_timeout' => 10000,
'read_timeout' => 10000
);
$pamiClient = new PamiClient($pamiClientOptions);
// Open the connection
$pamiClient->open();
$pamiClient->registerEventListener(function (EventMessage $event) {
var_dump($event);
});
$running = true;
// Main loop
while($running) {
$pamiClient->process();
usleep(1000);
}
// Close the connection
$pamiClient->close();
What I'm trying to do is implementing events&listeners in Laravel with PAMI. But it doesn't seem to work.
I have registered the event and listener on EventServiceProvider class:
protected $listen = [
'App\Events\AmiEventOccurred' => [
'App\Listeners\LogAmiEvent',
],
My event class:
<?php
namespace App\Events;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
class AmiEventOccurred
{
use Dispatchable, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
$pamiClientOptions = array(
'host' => getenv('PAMI_HOST'),
'scheme' => getenv('PAMI_SCHEME'),
'port' => getenv('PAMI_PORT'),
'username' => getenv('PAMI_USERNAME'),
'secret' => getenv('PAMI_SECRET'),
'connect_timeout' => getenv('PAMI_CONNECT_TIMEOUT'),
'read_timeout' => getenv('PAMI_READ_TIMEOUT'),
);
$pamiClient = new \PAMI\Client\Impl\ClientImpl($pamiClientOptions);
// Open the connection
$pamiClient->open();
$pamiClient->registerEventListener(function (\PAMI\Message\Event\EventMessage $message) {
$this->message = $message;
});
$running = true;
// Main loop
while($running) {
$pamiClient->process();
usleep(1000);
}
// Close the connection
$pamiClient->close();
}
}
My listener class:
<?php
namespace App\Listeners;
use App\Events\AmiEventOccurred;
class LogAmiEvent
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param AmiEventOccurred $event
* #return void
*/
public function handle(AmiEventOccurred $event)
{
Log::debug(print_r($event, true));
}
}
Is this a correct approach? Is it possible to actively listen to another service in real time with Laravel events, or should I develop another app for it to run in real time on the background and trigger Laravel events when necessary?
All of this code doesn't belong in an event. An event is just for announcing that something has happened, and containing all of the important information about what it was. Your code for running a client and sleeping and all of that should be somewhere else, likely an artisan command that your kernel keeps running. Then in that code, when something happens, you trigger a simple event with the message for any listeners to act on:
$pamiClient->registerEventListener(function (\PAMI\Message\Event\EventMessage $message) {
new AmiMessageReceived($message);
});

creation of unit tests with users

I want to test the next method of my controller
function index(){
if(Auth::User()->can('view_roles'))
{
$roles = Role::all();
return response()->json(['data' => $roles], 200);
}
return response()->json(['Not_authorized'], 401);
}
it is already configured for authentication (tymondesigns / jwt-auth) and the management of roles (spatie / laravel-permission), testing with postman works, I just want to do it in an automated way.
This is the test code, if I remove the conditional function of the controller the TEST passes, but I would like to do a test using a user but I have no idea how to do it.
public function testIndexRole()
{
$this->json('GET', '/role')->seeJson([
'name' => 'admin',
'name' => 'secratary'
]);
}
Depends on what kind of app are you building.
A - Using Laravel for the entire app
If your using Laravel for frontend/backend, well to simulate a logged-in user you could use the awesome Laravel Dusk package, made by the Laravel team. You can check the documentation here.
This package has some helpful methods to mock login sessions amongs a lot more of other things, you can use:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
That way you hit an endpoint with a logged-in user of id=1. And a lot more of stuff.
B - Using Laravel as a backend
Now, this is mainly how I use Laravel.
To identify a user that hits an endpoint, the request must send an access_token. This token helps your app to identify the user. So, you will need to make and API call to that endpoint attaching the token.
I made a couple of helper functions to simply reuse this in every Test class. I wrote a Utils trait that is being used in the TestCase.php and given this class is extended by the rest of the Test classes it will be available everywhere.
1. Create the helper methods.
path/to/your/project/ tests/Utils.php
Trait Utils {
/**
* Make an API call as a User
*
* #param $user
* #param $method
* #param $uri
* #param array $data
* #param array $headers
* #return TestResponse
*/
protected function apiAs($user, $method, $uri, array $data = [], array $headers = []): TestResponse
{
$headers = array_merge([
'Authorization' => 'Bearer ' . \JWTAuth::fromUser($user),
'Accept' => 'application/json'
], $headers);
return $this->api($method, $uri, $data, $headers);
}
protected function api($method, $uri, array $data = [], array $headers = [])
{
return $this->json($method, $uri, $data, $headers);
}
}
2. Make them available.
Then in your TestCase.php use the trait:
path/to/your/project/tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, Utils; // <-- note `Utils`
// the rest of the code
3. Use them.
So now you can do API calls from your test methods:
/**
* #test
* Test for: Role index
*/
public function a_test_for_role_index()
{
/** Given a registered user */
$user = factory(User::class)->create(['name' => 'John Doe']);
/** When the user makes the request */
$response = $this->apiAs($user,'GET', '/role');
/** Then he should see the data */
$response
->assertStatus(200)
->assertJsonFragment(['name' => 'admin'])
->assertJsonFragment(['name' => 'secretary']);
}
Side note
check that on top of the test methods there is a #test annotation, this indicates Laravel that the method is a test. You can do this or prefix your tests names with test_

Categories