Laravel 5.3 Queue Job is not working - php

I am trying to dispatch my send email action using Laravel database queue
however this process still continues in my browser instead of working behind.
this is my controller
protected function importUserExcel(UploadedFile $file, Request $request){
$user_role = Role::where('name','=','user')->first();
\Excel::load($file, function($reader) use ($user_role) {
$excel = $reader->select()->get();
foreach($excel[0] as $line){
$user = User::firstOrnew([
'email' => $line['email']]);
$user->email = $line['email'];
$user->name = $line['name'];
$user->password= bcrypt(srand(15));
$user->town = $line['town'];
$user->dealer_code = $line['dealer_code'];
$user->type = $line['type'];
// $user->save();
$user->sendUserEmail();
//$user->attachRole($user_role);
}
});
}
this is my model function
public function sendUserEmail()
{
$delay = Carbon::now()->addMinutes(15);
\Log::info("Request Begins");
$user = new SendEmails($this);
$user->delay($delay);
dispatch($user);
\Log::info("Request Ends");
}
and this is my job
class SendEmails implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(User $user)
{
$this->handle($user);
}
/**
* Execute the job.
*
* #return void
*/
public function handle(User $user)
{
$broker = $user->broker;
$brokerInstance = \Password::broker($broker);
view()->share('locale', app()->getLocale());
$response = $brokerInstance->sendResetLink([ 'email' => $user->email ], function (Message $message) {
$message->subject(trans('emails.welcome_subject'));
});
}
}
however result seems coming eventually not delaying or queueing anything.
Meanwhile my browser also process instead of putting process to behind.

Your job's constructor should not call the handle() method; it should just set properties needed for the handle method. It's up to your queue worker to call the handle method.
Your call to app()->getLocale() may be incorrect if you're setting the locale per-request; a job is executed from another process and without middlewares or an associated http request.
class SendEmails implements ShouldQueue { use InteractsWithQueue, Queueable, SerializesModels;
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function handle() {
$user = $this->user;
$broker = $user->broker;
$brokerInstance = \Password::broker($broker);
view()->share('locale', app()->getLocale());
$response = $brokerInstance->sendResetLink([ 'email' => $user->email ], function (Message $message) {
$message->subject(trans('emails.welcome_subject'));
});
}
}

You can try again in the following way (I assume that you did instructions in Laravel docs but someday it's not working):
drop table jobs in your database.
run command php artisan migrate in console
run command php artisan queue:work in console
retry your app

Related

Laravel Job for Bulk Mail

this is the first time I am using laravel queue jobs, and somehow i could not get it working.
This is my mail class:
class TopluKabulMektubu extends Mailable
{
use Queueable, SerializesModels;
public $letter;
public function __construct(AcceptLetter $letter)
{
$this->letter = $letter;
}
public function build()
{
$letter = $this->letter;
return $this->subject('Mail Title')
->view('emails.topluKabulSon')
->attach(public_path($letter->pdf), [
'as' => 'AcceptanceLetter.pdf',
'mime' => 'application/pdf',
]);
}
}
And I created a function inside my AcceptanceLetter model to use mail easier :
public function sendAcceptanceLetter(){
Mail::to('******#gmail.com')->queue(new TopluKabulMektubu($this));
if(Mail::failures()){
$this->email_send = 2;
$this->save();
}else{
$this->email_send = 1;
$this->save();
}
}
I created a queue table with php artisan queue:table and migrated, also changed queue connection to database from env file.
And my job file:
class QueueJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $letter;
public function __construct($letter)
{
//
$this->letter = $letter;
}
public function handle()
{
$this->letter->sendAcceptanceLetter();
}
}
Web route triggers my job :
Route::get('/topluDeneme', [PaginationController::class, 'topluQueue']);
And the controller:
public function topluQueue(){
$letters = AcceptLetter::where('email', '!=', null)->where('passport_number','!=','0');
foreach($letters as $letter){
QueueJob::dispatch($letter);
}
}
I expect when i run php artisan queue:listen on terminal and go to /topluDeneme route, mails to be sent. But nothing happens on terminal, mails not sent and nothing changes on job datatable.
I found whats wrong with my code. It seems I forgot to use get() in the controller, here is the correct version :
public function topluQueue(){
$letters = AcceptLetter::where('email', '!=', null)->where('passport_number','!=','0')->get();
foreach($letters as $letter){
QueueJob::dispatch($letter);
}
}

Symfony -Return json output of Command Class from Controller

In my Symfony project I have created a Command class to delete specific user.
I injected required parameter "email" in the Command class constructor.
I have never tried to implement command in the Controller so I have problem there.
I want to trigger the API call in the Controller which will return desired json output if command is successful.
How can I accomplish that?
My Command class:
protected static $defaultName = 'user:delete';
$entityManager;
private $userService;
private $email;
public function __construct(string $email = null, EntityManagerInterface $entityManager, KeycloakApi $keycloakApi)
{
parent::__construct($email);
$this->entityManager = $entityManager;
$this->userService = $userService;
}
protected function configure()
{
$this
->setDescription('Deletion of selected user.')
->addArgument('email', InputArgument::REQUIRED, 'User email');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$user = $this->userService->getUserByEmail($this->email);
if (empty($user)) {
throw new Exception('USER_DOESNT_EXIST');
}
$this->userService->deleteUser($user['id']);
$output->writeln('Done!');
}
And my try in controller to get what I want:
/**
* #Route("/delete/test", name="delete_test")
*/
public function testDelete(): JsonResponse
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array("user:delete"));
$output = new BufferedOutput();
// Run the command
$retval = $application->run($input, $output);
dump($retval);die;
}
And the main question is how to pass email parameter in command that is needed to be provided for this endpoint?
As the comments said, you shouldn't call a command from a controller, both are a different entry point of your application. Controller are used to access from the web and Command to be executed from a cli.
You should put your domain logic in neither of those files.
Here's an example which is possible :
Controller :
/**
* #Route("/delete/{email}", name="delete")
*/
public function delete(string $email, DeleteUserHandler $handler): JsonResponse
{
$handler->handle($email);
return new JsonReponse(null, 204);
}
DeleteUserHandler :
...
public function handle(string $email): void
{
$user = $this->userService->getUserByEmail($this->email);
if (empty($user)) {
throw new Exception('USER_DOESNT_EXIST');
}
$this->userService->deleteUser($user['id']);
}
I kept a bit of you code but IMO you don't even need to find before the delete (just maybe add a security to avoid anyone to delete anyone)
With this kind of code you can reuse the "DeleteUserHandler" in a command (or wherever) if you need it and the code which really delete a user isn't coupled with the entry point anymore

Laravel 7 set log path dynamically in Job class

Im building project on Laravel 7.3 with multiple Jobs that run at the same time.
I need to make each Job write logs to different daily rotated file. The name of the log file should be based on model, that Job is processing.
The issue is I cant find smart solution.
What I have tried:
1) creating multiple channels in config/logging.php.
That works as expected but at the moment there are about 50 different Jobs and amount keeps growing. Method is ugly and hardly maintained.
2) setting up Config(['logging.channels.CUSTOMCHANNEL.path' => storage_path('logs/platform/'.$this->platform->name.'.log')]);.
Messing with Config variable was bad idea because of many Jobs running one time. As a result messages from one job often were written in another Job log.
3) using Log::useDailyFiles()
Seems like this stops working since laravel 5.5 or 5.6. Just getting error Call to undefined method Monolog\Logger::useDailyFiles(). Any thoughts how to make with work in laravel 7?
4) using tap parameter for channel in config/logging.php.
Example in laravel docs
No ideas how to pass model name into CustomizeFormatter to setup file name.
Im almost sure there is smart solution and Im just missing something.
Any suggests? Thanks!
You could inherit the log manager to allow a dynamic configuration
<?php
namespace App\Log;
use Illuminate\Support\Str;
use Illuminate\Log\LogManager as BaseLogManager;
class LogManager extends BaseLogManager
{
/**
* Get the log connection configuration.
*
* #param string $name
* #return array
*/
protected function configurationFor($name)
{
if (!Str::contains($name, ':')) {
return parent::configurationFor($name);
}
[$baseName, $model] = explode(':', $name, 2);
$baseConfig = parent::configurationFor($baseName);
$baseConfig['path'] = ...; //your logic
return $baseConfig;
}
}
Likewise about Laravel's log service provider except this one can be totally replaced
<?php
namespace App\Log;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->singleton('log', function ($app) {
return new LogManager($app);
});
}
}
EDIT: I've just seen that Laravel's log service provider is missing from config/app.php, this is because it's "hard-loaded" by the application. You still can replace it by inheriting the application itself
<?php
namespace App\Foundation;
use App\Log\LogServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Illuminate\Foundation\Application as BaseApplication;
class Application extends BaseApplication
{
/**
* Register all of the base service providers.
*
* #return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
And finally in bootstrap/app.php, replace Illuminate\Foundation\Application with App\Foundation\Application
For example, if you try this
app('log')->channel('single:users')->debug('test');
Laravel will use the single channel's config and write to users.log if your resolution logic is
$baseConfig['path'] = $model + '.log';
I got a solution that I've been using since Laravel 4 that works, although it doesn't follow 'Laravel' way of doing things.
class UserTrackLogger
{
/**
* #var $full_path string
*/
protected $full_path;
/**
* #var $tenant string
*/
protected $tenant;
/**
* #var $user User
*/
protected $user;
/**
* #var $request Request
*/
protected $request;
public static function log(string $message, Request $request, User $user, array $data = []): void
{
/** #noinspection PhpVariableNamingConventionInspection */
$userTrack = new static($request, $user);
$userTrack->write($message, $data);
}
protected function __construct(Request $request, User $user)
{
$this->request = $request;
$this->user = $user;
$this->tenant = app()->make('tenant')->tenant__name;
$path = storage_path() . "/logs/{$this->tenant}/users";
$filename = $this->user->username_with_name;
$this->full_path = Formatter::formatPath("{$path}/{$filename}.log");
self::makeFolder($this->full_path);
}
protected function write(string $message, array $data = []): void
{
$formatter = $this->getFormat();
$record = [
'message' => $message,
'context' => $data,
'extra' => [],
'datetime' => date(Utility::DATETIME_FORMAT_DEFAULT),
'level_name' => 'TRACK',
'channel' => '',
];
file_put_contents($this->full_path, $formatter->format($record), FILE_APPEND);
}
protected function getFormat(): FormatterInterface
{
$ip = $this->request->getClientIp();
$method = strtoupper($this->request->method());
$format = "[%datetime%][{$this->tenant}][{$this->user->username}][{$this->user->name}]: $ip $method %message% %context%\n";
return new LineFormatter($format, null, true);
}
protected static function makeFolder(string $full_path): bool
{
$path = dirname($full_path);
if ( !is_dir($path) ) {
return mkdir($path, 0755, true);
}
return false;
}
}
And when I want to log something, I do UserTrackLogger::log($request->fullUrl(), $request, $user, $data);
What I would suggest is creating a logger similar to this but extends RotatingFileHandler.

Laravel Queue How to get data in job handle method

I have created a sequence of messsages to be sent in text messages through twilio.
I have created a controller to put the messages in a queue with the data received in the post request. Here is my controller to make queue:
public function make_queue(Request $request)
{
$data = array (
'phone' => $request->input('phone'),
'message'=> $request->input('message'),
'token'=> $request->input('token')',
'sid'=> $request->input('sid')
);
ProcessMessages::dispatch($data)
->delay(now()->addSeconds(15));
return 'message will be sent';
}
And in handle the job , in the handle function
public function handle()
{
$token = should_come_from job;
$sid = should_come_from job;
$ids = should_come_from job;
$msg = should_come_from job;
try{
// send message
}
catch (exception $e)
{
handle exception
}
}
I am not able to figure out how do I get the values in the handle function to actually send the message....
You need to add a constructor method in your job handler class (ProcessMessages), for example:
// namespace and use statements...
class ProcessMessages implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function handle()
{
$token = $this->data['token'];
// ...
}
}
Once you have written your job class, you may dispatch it using the
dispatch method on the job itself. The arguments passed to the
dispatch method will be given to the job's constructor. Read about dispatching Jobs.

Laravel Event listener and caching not working

I am facing some difficulties while developing an app on Laravel.
I want to use Event and Listener to delete and rebuild the cache of an object.
Here is the code:
app\Events\CampaignEvent.php
namespace App\Events;
use Illuminate\Queue\SerializesModels;
class CampaignEvent extends Event
{
use SerializesModels;
public $user_id;
public $cache_keys;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($user_id, $cache_keys)
{
$this->user_id = $user_id;
$this->cache_keys = $cache_keys;
}
}
app\Listenters\CampaignListener.php
<?php
namespace App\Listeners;
use App\Events\CampaignEvent;
use Cache;
use Log;
use App\BrandCampaign;
class CampaignListener
{
/**
* Handle the event.
*
* #param CampaignEvent $event
* #return void
*/
public function handle(CampaignEvent $event)
{
/**
* Remove cache
*/
if(is_array($event->cache_keys)){
foreach($event->cache_keys as $index => $cache_key){
\Cache::forget($cache_key);
Log::debug("[CACHE] Deleted cache for: " . $cache_key);
}
} else {
\Cache::forget($event->cache_keys);
Log::debug("[CACHE] Deleted cache for: " . $event->cache_keys);
}
/**
* Rebuild cache for BrandCampaigns
*/
$campaigns = BrandCampaign::with(['influencers' => function($query){
$query->with(['influencer' => function($query){
$query->select('id','profile_picture');
}])->latest();
}])->where('user_id', $event->user_id )->latest()->get();
$total_influencers = [];
foreach($campaigns as $campaign){
foreach ($campaign->influencers as $influencer) {
if(!in_array($influencer->influencer_id, $total_influencers))
$total_influencers[] = $influencer->influencer_id;
}
}
$total_influencers = count($total_influencers);
$campaigns = collect($campaigns)->toArray();
\Cache::forever('#suppliers_campaigns('.$event->user_id.')', $campaigns);
\Cache::put('#suppliers_total_campaigns('.$event->user_id.')', $total_influencers, 10);
Log::debug("[CACHE] Cache rebuilt successfully!");
return $event;
}
}
I want to cache an array "forever", but in my campaign controller, after the event is fired, when I pull the array from cache it is returning null
Thanks!
Works in Laravel 5 (based on the question) & Laravel 7 (latest) as well.
use Illuminate\Support\Facades\Cache;
// Remove cache
Cache::forget('brandCampaigns');
// Rebuild cache for BrandCampaigns. Here, when the cache key doesn't exists, the function will be called and the returned value will be stored in the cache
$campaigns = Cache::rememberForever('brandCampaigns', function () {
return BrandCampaign::with(['influencers' => function ($query) {
$query->with(['influencer' => function ($query) {
$query->select('id', 'profile_picture');
}])->latest();
}])->where('user_id', $event->user_id)->latest()->get();
});
It is important to enable discovery in EventServiceProvider class.
-> app/Providers/EventServiceProvider.php
public function shouldDiscoverEvents()
{
return true;
}
make sure this function return true, otherwise events and listeners don't find together.

Categories