Push notification for android and ios in laravel with queue - php

I am using davibennun/laravel-push-notification package for sending notification to device. But I want to send notification to multiple users for that i want to use laravel queue. But i am new to laravel that's why don't know how to use queue with notification.
I have created the migrate the queue table and have created the job by
php artisan make:job SendPushNotification command.

After running following command
php artisan make:job SendPushNotification
From your controller
$user = User::all();
$other_data = array('message' => 'This is message');
SendPushNotification::dispatch($user, $other_data);
In app\Jobs\SendPushNotification.php
protected $contacts;
protected $sms_data;
public function __construct($users, $other_data)
{
//
$this->users = $users;
$this->other_data = $other_data;
}
public function handle()
{
$users = $this->users;
$other_data = $this->other_data;
foreach($users as $key => $value){
// You code
}
Run following command
php artisan queue:work

Related

Laravel Schedule not sending email

It's my first time trying to implement Task Scheduling, I'm trying to send automatic E-mails at a certain time:
Before implementing my cron I first tested my email sending code manually in a normal class to see if there is no error, and there was no error, the email was sent successfully.
After that, I started implementing the Task Scheduling
Democron.php
protected $signature = 'demo:cron';
protected $description = 'Command description';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$tasks = Task::all();
$date = Carbon::now()->toDateTimeString();
foreach ($tasks as $task) {
if($task->completed_at != null){
$validad = $task->completed_at;
$receiver_id = User::findOrFail($task->user_id);
if($date > $validad){
$details = [
'task_id' =>$task->id,
'receiver_id' => $receiver_id
];
$subject = 'TeamWork - Você tem tarefas em atraso!';
$view = 'emails.project.delaydtask';
Mail::to($receiver_id->email)->send(new SendMail($details, $subject, $view));
Log::info('Email enviado com sucesso para '.$receiver_id->email);
}
}
}
}
Kernel.php
protected $commands = [
DemoCron::class,
];
protected function schedule(Schedule $schedule)
{
$schedule->command('demo:cron')
->twiceDaily(12, 15)
->timezone('Africa/Maputo');
}
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
I added to CRON JOBS on CPANEL
and set twiceDaily at 12 and 15
/usr/local/bin/php /.......myProjectPath/artisan schedule:run >> /dev/null 2>&1
I printed a LOG in my DemoCron.php to see if it really works
Result 1: when I select schedule once per minute it prints my LOG respecting all the conditions that are in my Democron.php , but it doesn't send the email.
Result 2: When I select a certain time (Twice per day or once a day) my LOG does not print anything and it does not send the email.
What am I doing wrong? Help me please!
UPDATE
my SendMail class that i use to send emails manually works perfectly,
but the scheduled emails are not going
class SendMail extends Mailable
{
use Queueable, SerializesModels;
public $details, $subject, $view;
public function __construct($details, $subject, $view)
{
$this->details = $details;
$this->subject = $subject;
$this->view = $view;
}
public function build()
{
return $this->subject($this->subject)
->view($this->view, ['details' => $this->details]);
}
}
After trying several times I found a workaround.
1- create a new controller
I created a new controller called MailController instead of using the Kernel.php and Democron.php classes that I generated through Laravel Scheduling
class MailController extends Controller
{
public function delayedtask(){
try {
$tasks = Task::all();
$date = Carbon::now()->toDateTimeString();
foreach ($tasks as $task) {
if($task->completed_at != null){
$validad = $task->completed_at;
$receiver_id = User::findOrFail($task->user_id);
if($date > $validad){
$details = [
'task_id' =>$task->id,
'receiver_id' => $receiver_id
];
$subject = 'TeamWork - Você tem tarefas em atraso!';
$view = 'emails.project.delaydtask';
Mail::to($receiver_id->email)->send(new SendMailQueue($details, $subject, $view));
Log::info('Email enviado com sucesso para '.$receiver_id->email);
}
}
}
return "Done!";
} catch (Exception $e) {
return "Something went wrong!";
}
}
}
2-add a new route
added a new route without Auth
Route::get('/delayedtask',[MailController::class, 'delayedtask']);
3-Added a cronjob on Cpanel
curl -s "https://myWebsiteURL/delayedtask">/dev/null 2>&1
First of all lets check all things:
Verify your mail configurations in your .env;
Verify in your email class if have implements ShouldQueue;
If you are implementing ShouldQueue, you must have to verify too your queue´s configuration in .env;
If is not implementing ShouldQueue, don´t miss time verifying queue´s config;
All right all things validated and still not sending email:
Add the Send mail in try catch and log the catch if something went wrong;
If don´t log nothing in try catch, try to create an command that just send a simple email;
If dosen´t work try to send an email by your mail in Cpanel, because this should be the problem;
Finally
In my cases using cPanel, I always create the croon task to all seconds like * * * * * and in the kernel of my laravel project I verify if some command must be executed with the laravel commands like ->twiceDaily(12, 15).
Try all things and if the error still, please update this thread!
I had the same problem,
i tried a new smtp email server
MAIL_HOST=pro.eu.turbo-smtp.com
MAIL_ENCRYPTION=ssl
instead of
MAIL_HOST=smtpauth.online.net
MAIL_ENCRYPTION=tls
I don't know if it's about the encryption or host features,
but it worked for me

Task scheduling in laravel forge

I've hosted my app in Digital Ocean with Laravel Forge and have created a artisan command to run through schedule [Nightly].
The artisan command runs smoothly via terminal but returns No such file or directory while running it through Scheduled Jobsmanually from Laravel Forge.
The command for the job is:
php /home/forge/sitename.com/artisan Instagram:fetch
The artisan command php /home/forge/sitename.com/artisan Inspire i.e php artisan inspire returns the correct output.
While the above mentioned custom artisan returns No such file or directory
app\Console\Commands\FetchInstagram.php's handel method:
public function handle()
{
$instagram = new Instagram('api-key');
$posts = $instagram->media(); //gets 20 latest insta posts of a user
// dd($posts);
$fromDBs = Insta::orderBy('id', 'desc')->take(20)->get(); //get last 20 rows from table
foreach( $posts as $post)
{
Insta::firstOrCreate([
'thumb_link' => $post->images->thumbnail->url ,
'standard_link' => $post->images->standard_resolution->url ,
'caption' => $post->caption->text
]);
}
}
Code from Kernel.php:
protected $commands = [
'App\Console\Commands\FetchInstagram'
];
protected function schedule(Schedule $schedule)
{
$schedule->command('Instagram:fetch')
->daily();
}

Symfony Swiftmailer stop sending process to show status

In our application, for some actions we send out notifications and emails to a rather large number of users (several hundred to thousand). For some purposes we have to send these emails/notifications separately which I did, using a foreach loop.
For about 200-300 users, that's working fine, but as soon as there are more users to be notified, we get a timeout after a while.
What I was now thinking to do, is redirect to a new page, after e.g. a document is created, and handle the email/notification send out there by sending out let's say 20 emails, then display an update like "20 out of xxx emails/notifications have been sent out", then continue sending the next 20, displaying an update and so on.
Is there any body who already did something like this or has ideas on that?
Here is my code so far:
{
document gets created...
$em->persist($document);
$em->flush();
$this->addFlash(
'success',
'Your document has been created!'
);
return $this->redirectToRoute('documentBundle_document_send_notifications', array('id' => strtok($document->getId(), '_')));
}
/**
* #Route("/document/sendNotifications/{id}", name="documentBundle_document_send_notifications", requirements={"id" = "\d+"})
*/
public function sendDocumentNotifications($id){
$em = $this->getDoctrine()->getManager();
$document = $em->getRepository('DocumentBundle:Document')->findOneById($id);
$statusRepository = $em->getRepository('DocumentBundle:Status');
/*
* NOTIFICATION MANAGEMENT
*/
//Users
$user = $em->getRepository('UserBundle:User');
$user = $user->findByDocumentAgency($user, $document);
$users = array_unique($user->getResult());
if(count($users)>0){
/*
* SEND NOTIFICATION
*/
foreach ($users as $user){
$manager = $this->get('mgilet.notification');
$notif = $manager->generateNotification('A Document has been created!');
$manager->addNotification($user, $notif);
}
/*
* SEND EMAIL
*/
foreach ($users as $user){
$recipient = $user->getEmail();
$this->get('MailerHelper')->sendMessage($recipient,...);
}
}
}
return $this->redirectToRoute('documentBundle_document_list');
}
EDIT
added a console command to run as a background process
/**
* #Route("/document/sendNotifications/{id}", name="documentBundle_document_send_notifications", requirements={"id" = "\d+"})
*/
public function sendDocumentNotifications($id){
$em = $this->getDoctrine()->getManager();
$document = $em->getRepository('DocumentBundle:Document')->findOneById($id);
$process = new Process('php app/console app:send-notifications', $id );
$process->run();
dump($process);
return $this->redirectToRoute('documentBundle_document_bulkDeactivate');
}
when dumping the process, I get the correct commandLine and the input is the correct document Id, but it says:
Process {#1539 ▼
-callback: null
-commandline: "php app/console app:send-notifications"
-cwd: "/srv/http/sp/web"
-env: null
-input: "320"
-starttime: 1511378616.9664
-lastOutputTime: 1511378616.9852
-timeout: 60.0
-idleTimeout: null
-options: array:2 [▼
"suppress_errors" => true
"binary_pipes" => true
]
-exitcode: 1
-fallbackStatus: []
-processInformation: array:8 [▼
"command" => "php app/console app:send-notifications"
"pid" => 29887
"running" => false
"signaled" => false
"stopped" => false
"exitcode" => 1
"termsig" => 0
"stopsig" => 0
I then tried it with
$process->mustRun() and gut the following error:
The command "php app/console app:send-notifications" failed.
Exit Code: 1(General error)
Working directory: /srv/http/sp/web
Output:
================ Could not open input file: app/console
Error Output:
500 Internal Server Error - ProcessFailedException
TL;DR: to prevent time-outs, offload your process to a background process.
First step: use a spool
A quick win would be to Spool Emails:
When you are using the SwiftmailerBundle to send an email from a
Symfony application, it will default to sending the email immediately.
You may, however, want to avoid the performance hit of the
communication between Swift Mailer and the email transport, which
could cause the user to wait for the next page to load while the email
is sending.
The actual mails will be sent by a command line script:
php bin/console swiftmailer:spool:send --env=prod.
Next step: create a command that handles notifications
Learn how to create a console command. Something like this (not tested, just as an example):
class NotificationCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
// the name of the command (the part after "bin/console")
->setName('app:send-notifications')
// the short description shown while running "php bin/console list"
->setDescription('Send notifications to information users about a new document.')
->addArgument('document', InputArgument::REQUIRED, 'The ID of the new document.')
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$document = $em->getRepository('DocumentBundle:Document')->findOneById($input->getArgument('document'));
$statusRepository = $em->getRepository('DocumentBundle:Status');
/*
* NOTIFICATION MANAGEMENT
*/
//Users
$user = $em->getRepository('UserBundle:User');
$user = $user->findByDocumentAgency($user, $document);
$users = array_unique($user->getResult());
if(count($users)>0){
/*
* SEND NOTIFICATION
*/
foreach ($users as $user){
$manager = $this->get('mgilet.notification');
$notif = $manager->generateNotification('A Document has been created!');
$manager->addNotification($user, $notif);
}
/*
* SEND EMAIL
*/
foreach ($users as $user){
$recipient = $user->getEmail();
$this->get('MailerHelper')->sendMessage($recipient,...);
}
}
}
}
}
To execute this, run bin/console app:send-notifications 1 on your command line.
Schedule your command
I guess you don't want to log into the server manually to send the notifications, so instead you can create a general 'send notifications if needed' command that can scheduled by a cron job.
As a next step you can take a look at a way to queue your commands. Take JMSJobQueueBundle for example.
Bonus tip: start by making your controllers thinner
Try to make your controllers thinner. Read this old, but still interesting blog. If you create services and call them from the controller (instead of having a fat controller), the 'refactoring' job of removing code from controller to background job (and maybe visa versa) is much easier.

Manually register a user in Laravel

Is it possible to manually register a user (with artisan?) rather than via the auth registration page?
I only need a handful of user accounts and wondered if there's a way to create these without having to set up the registration controllers and views.
I think you want to do this once-off, so there is no need for something fancy like creating an Artisan command etc. I would suggest to simply use php artisan tinker (great tool!) and add the following commands per user:
$user = new App\Models\User();
$user->password = Hash::make('the-password-of-choice');
$user->email = 'the-email#example.com';
$user->name = 'My Name';
$user->save();
This is an old post, but if anyone wants to do it with command line, in Laravel 5.*, this is an easy way:
php artisan tinker
then type (replace with your data):
DB::table('users')->insert(['name'=>'MyUsername','email'=>'thisis#myemail.com','password'=>Hash::make('123456')])
Yes, the best option is to create a seeder, so you can always reuse it.
For example, this is my UserTableSeeder:
class UserTableSeeder extends Seeder {
public function run() {
if(env('APP_ENV') != 'production')
{
$password = Hash::make('secret');
for ($i = 1; $i <= 10; $i++)
{
$users[] = [
'email' => 'user'. $i .'#myapp.com',
'password' => $password
];
}
User::insert($users);
}
}
After you create this seeder, you must run composer dumpautoload, and then in your database/seeds/DatabaseSeeder.php add the following:
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Model::unguard();
$this->call('UserTableSeeder');
}
}
Now you can finally use php artisan db:seed --class=UserTableSeeder every time you need to insert users in the table.
You can also create a new console command which can be called from the command line. This is especially useful if you want to create new users on demand.
This example makes use of laravel fortify but you can also use your own user registration logic.
First create a new console command:
php artisan make:command CreateUserCommand
Then add the implementation:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Actions\Fortify\CreateNewUser;
class CreateUserCommand extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'user:create {--u|username= : Username of the newly created user.} {--e|email= : E-Mail of the newly created user.}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Manually creates a new laravel user.';
/**
* Execute the console command.
* https://laravel.com/docs/9.x/artisan
*
* #return int
*/
public function handle()
{
// Enter username, if not present via command line option
$name = $this->option('username');
if ($name === null) {
$name = $this->ask('Please enter your username.');
}
// Enter email, if not present via command line option
$email = $this->option('email');
if ($email === null) {
$email = $this->ask('Please enter your E-Mail.');
}
// Always enter password from userinput for more security.
$password = $this->secret('Please enter a new password.');
$password_confirmation = $this->secret('Please confirm the password');
// Prepare input for the fortify user creation action
$input = [
'name' => $name,
'email' => $email,
'password' => $password,
'password_confirmation' => $password_confirmation
];
try {
// Use fortify to create a new user.
$new_user_action = new CreateNewUser();
$user = $new_user_action->create($input);
}
catch (\Exception $e) {
$this->error($e->getMessage());
return;
}
// Success message
$this->info('User created successfully!');
$this->info('New user id: ' . $user->id);
}
}
You can execute the command via:
php artisan user:create -u myusername -e mail#example.com
I recommend to always ask for the password via the user input, not as parameter for security reasons.
Yes, you can easily write a database seeder and seed your users that way.
You can use Model Factories to generate a couple of user account to work it. Writing a seeder will also get the job done.

Laravel 5 console (artisan) command unit tests

I am migrating my Laravel 4.2 app to 5.1 (starting with 5.0) and am a lot of trouble with my console command unit tests. I have artisan commands for which I need to test the produced console output, proper question/response handling and interactions with other services (using mocks). For all its merits, the Laravel doc is unfortunately silent with regards to testing console commands.
I finally found a way to create those tests, but it feels like a hack with those setLaravel and setApplication calls.
Is there a better way to do this? I wish I could add my mock instances to the Laravel IoC container and let it create the commands to test with everything properly set. I'm afraid my unit tests will break easily with newer Laravel versions.
Here's my unit test:
Use statements:
use Mockery as m;
use App\Console\Commands\AddClientCommand;
use Symfony\Component\Console\Tester\CommandTester;
Setup
public function setUp() {
parent::setUp();
$this->store = m::mock('App\Services\Store');
$this->command = new AddClientCommand($this->store);
// Taken from laravel/framework artisan command unit tests
// (e.g. tests/Database/DatabaseMigrationRollbackCommandTest.php)
$this->command->setLaravel($this->app->make('Illuminate\Contracts\Foundation\Application'));
// Required to provide input to command questions (provides command->getHelper())
// Taken from ??? when I first built my command tests in Laravel 4.2
$this->command->setApplication($this->app->make('Symfony\Component\Console\Application'));
}
Input provided as command arguments. Checks console output
public function testReadCommandOutput() {
$commandTester = new CommandTester($this->command);
$result = $commandTester->execute([
'--client-name' => 'New Client',
]);
$this->assertSame(0, $result);
$templatePath = $this->testTemplate;
// Check console output
$this->assertEquals(1, preg_match('/^Client \'New Client\' was added./m', $commandTester->getDisplay()));
}
Input provided by simulated keyboard keys
public function testAnswerQuestions() {
$commandTester = new CommandTester($this->command);
// Simulate keyboard input in console for new client
$inputs = $this->command->getHelper('question');
$inputs->setInputStream($this->getInputStream("New Client\n"));
$result = $commandTester->execute([]);
$this->assertSame(0, $result);
$templatePath = $this->testTemplate;
// Check console output
$this->assertEquals(1, preg_match('/^Client \'New Client\' was added./m', $commandTester->getDisplay()));
}
protected function getInputStream($input) {
$stream = fopen('php://memory', 'r+', false);
fputs($stream, $input);
rewind($stream);
return $stream;
}
updates
This doesn't work in Laravel 5.1 #11946
I have done this before as follows - my console command returns a json response:
public function getConsoleResponse()
{
$kernel = $this->app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArrayInput([
'command' => 'test:command', // put your command name here
]),
$output = new Symfony\Component\Console\Output\BufferedOutput
);
return json_decode($output->fetch(), true);
}
So if you want to put this in it's own command tester class, or as a function within TestCase etc... up to you.
use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Console\Output\BufferedOutput;
$output = new BufferedOutput();
Artisan::call('passport:client', [
'--password' => true,
'--name' => 'Temp Client',
'--no-interaction' => true,
], $output);
$stringOutput = $output->fetch();

Categories