I know how to use ShouldQueue my question is about why does it work the way it does.
I need to edit how my new Job is stored in the database, and therefore am digging through Laravel's internals.
The job I want to edit is launched from the following event listener:
<?php
namespace App\Listeners;
use App\Events\NewMail;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Jobs\SendEmail;
use Carbon\Carbon;
class NewMailListener implements ShouldQueue
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param NewMail $event
* #return void
*/
public function handle(NewMail $event)
{
$addressee = $event->user->name;
$address = $event->user->email;
$type = "NewMail";
$job = (new SendEmail($type,$addressee,$address))->delay(Carbon::now()->addMinutes(10));
dispatch($job);
}
}
What I don't understand is how the ShouldQueue magic works, because in the source code it appears to do nothing.
<?php
namespace Illuminate\Contracts\Queue;
interface ShouldQueue
{
//
}
I understand it is a contract but it's not defining anything... so what it is doing exactly? Is there some auto-loading happening from the namespace?
I wasn't sure what an interface was exactly, so I looked at this: PHP Docs: Interfaces and came away with the impression that even if it is for decoupling, and interface should be defining something, which I don't see in ShouldQueue.
The top comment on that PHP docs page says this:
An INTERFACE is provided so you can describe a set of functions and
then hide the final implementation of those functions in an
implementing class. This allows you to change the IMPLEMENTATION of
those functions without changing how you use it.
But where is this description of functions here?
PS - I know this interface/contract is being used to queue the event listener itself, not the job that I want to edit. But I'm hoping understanding how the queue interacts with the event listener will better inform me as to how it works for the jobs.
Internally Laravel checks if Job or Mailable or Notification etc implements ShouldQueue interface. For example:
if ($job instanceof ShouldQueue) {
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Console/Scheduling/Schedule.php#L86
Related
Using lumen 8.2.3 I am only trying to dispatch a unique job to a queue. In app/Console/Kernel I have sent a schedule to $schedule->job(new myJob(), 'high')->everyMinute(); this runs every minutes.
In the job itself I have added the ShouldBeUnique interface class in myJob class I even added
public function uniqueId() {
return $this->process->id();
}
when my cron job runs for php artisan schedule:run, this is still creating multiple jobs in the queue causing my 3 workers to pick up both jobs at the same time and causing issues.
https://laravel.com/docs/8.x/queues#unique-jobs clearly says
"Sometimes, you may want to ensure that only one instance of a
specific job is on the queue at any point in time. You may do so by
implementing the ShouldBeUnique interface on your job class. This
interface does not require you to define any additional methods on
your class:"
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;
class myJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue, Queueable, SerializesModels;
private $process;
public function __construct($process){
$this->process=$process;
}
public function uniqueId() {
return $this->process->id();
}
public function handle()
{
//some code here
}
public function failed(Throwable $exception)
{
// Send user notification of failure, etc...
}
}
Is the no way to prevent this? thank you
The code provided barely reflects what you are writing; there's no job at all.
The declaration should look like this:
class SomeJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue, Queueable, Dispatchable;
...
}
odd how this got fixed but what I did was change the version from 8.2.4 to 8.3.4 and shouldBeUnique work looks like a bug was introduced in 8.3.4
I am using a php script to scrape websites (hQuery by Duzun). I have a form that has an input where someone can paste the URL they want scraped and a submit button to trigger the scraping. How can I set up the code below so that when a user clicks submit, the hQuery script picks up the URL they have put in the input and runs the script.
So far I have tried using the action() helper in Laravel to trigger a method in a ScraperController but that doesn't seem to be working. I get an error saying the postScrape method has not been defined.
<form action="{{ action('ScraperController#postScrape') }}" method="POST">
route::post('/', 'ScraperController#postScrape');
I am hoping that the user can click the button, their URL is passed to the script and it can then scrape the website they have linked to.
Because scraping is a potentially resource heavy and long-running task, you should delegate this to a queued job. You can read about queues and jobs here: https://laravel.com/docs/master/queues
I'd recommend saving the user's scrape request as a model entry. That way you can track the status (running, errored, completed, etc.), display it in a listing of tasks, attribute system load to users, and so on. Something like ScrapeTask (id, user_id, url, status, timestamps, and whatever other fields you'll need).
Once you have your model and your queue set up, you will then create a job class. This can be done by running php artisan make:job ScrapeWebsite, or you can create it manually.
app/Jobs/ScrapeWebsite.php
<?php
namespace App\Jobs;
use App\ScrapeTask;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ScrapeWebsite implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** #var ScrapeTask */
protected $scrapeTask;
/**
* Create a new job instance.
*
* #param ScrapeTask $scrapeTask
*/
public function __construct(ScrapeTask $scrapeTask)
{
$this->scrapeTask = $scrapeTask;
// also inject any necessary third party libraries, etc.
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
// scrape the website at $this->scrapeTask->url
}
}
Your controller method would look something like this:
app/Http/Controllers/ScraperController.php
<?php
namespace App\Http\Controllers;
use Auth;
use App\ScrapeTask;
use App\Jobs\ScrapeWebsite;
use Illuminate\Http\Request;
class ScraperController extends Controller
{
public function postScrape(Request $request)
{
// perform input validation, etc.
// create a new scrape task
$scrapeTask = ScrapeTask::create([
'user_id' => Auth::id(),
'url' => $request->input('url'),
]);
// dispatch the ScrapeWebsite job to the queue
dispatch(new ScrapeWebsite($scrapeTask));
// redirect to a scrape status monitoring page (or do whatever...)
return redirect()->route('scrape.monitor', $scrapeTask->id);
}
}
I would highly recommend setting up failed job tracking (https://laravel.com/docs/master/queues#dealing-with-failed-jobs) to make sure that you know when things aren't working.
I was following a tutorial to get Laravel to broadcast real-time but got stuck after just a few minutes of following along. Laravel throws the following message to me "Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, integer given, called in /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php on line 775"
I've tried to redo the tutorial several times to make sure that I didn't miss a thing but the problem is still the same after several tries.
Even checking the stack trace and documentation didn't give me any clue.
I've uncommented the following line in config/app.php:
App\Providers\BroadcastServiceProvider::class,
I've added the following lines to App\Providers\EventServiceProvider:
use App\Events\RideCreated;
use App\Listeners\RideCreatedListener;
and the following after protected $listen = [ in the same file
RideCreated::class => [
RideCreatedListener::class,
],
this is the setup of the route used for testing (web.php):
Route::get('/test', function(){
event(new RideCreated());
return "test";
});
and this is how RideCreated.php looks like:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class RideCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('RideCreated');
}
}
the listener (RideCreatedListener.php) looks like this:
<?php
namespace App\Listeners;
use App\Events\RideCreated;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class RideCreatedListener
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param RideCreated $event
* #return void
*/
public function handle(RideCreated $event)
{
//
}
}
I expected when visiting the /test rout to see test on screen but actually got the error message displayed.
my first thought was that the ShouldBroadcast implementation in RideCreated.php somehow causes the problem since removing implement ShouldBroadcast makes the error disappear. the only problem is that removing it is no option since it's needed for Pusher to work.
This may sound strange, but we have been resolving this issue f2f. My answer is just for other people that might read this.
It turned out that the queue was not configured such that a default queue could be resolved by Laravel. The error was fixed by adding the $bradcastQueue property to the RideCreated class. See Broadcast Queue
In my app I have a service called "LogService" to log events and other items. I basically need to use this on every controller to log events by users. Instead of having to instantiate this service in each controller, I had two thoughts for accomplishing this.
Option 1: Bind the service into the IoC and then resolve it that way
Option 2: Make a master class with the service in it and then extend it for other classes so they come with the service already bound
I have questions for each of these methods:
Option 1: Is this even possible? If so, would it just be with "App::make()" that it would be called? That way doesn't seem to play too well with IDE's
Option 2: I have done this kind of thing in the past but PHPStorm does not seem to recognize the service from the parent object because it is instantiated by "App::make()" and not through the regular dependency injection.
What would be the best course of action?
Thanks!
You can have it both ways, I think the neatest way would be:
1) Have an interface that describes your class, let's call it LogServiceInterface
2) Create a Service Provider that instantiates your class, like so:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class LoggerServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind(LogServiceInterface::class, function($app)
{
return new LogService();
});
}
}
3) Register this service provider in config/app.ph file:
'providers' => [
// Other Service Providers
App\Providers\LoggerServiceProvider::class,
],
4) Now, in controller you can request the instance of something that implements LoggerServiceInterface straight in the constructor:
(Some controller):
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Repositories\OrderRepository;
class OrdersController extends Controller {
/**
* The logger service.
* #var LoggerServiceInterface $loggerService
*/
protected $loggerService;
/**
* Create a controller instance.
*
* #param OrderRepository $orders
* #return void
*/
public function __construct(LoggerServiceInterface $loggerService)
{
$this->loggerService = $loggerService;
}
/**
* Show all of the orders.
*
* #return Response
*/
public function index()
{
// $this->loggerService will be an instance of your LoggerService class that
// is instantiated in your service provider
}
}
This way, you have got an easy way to quickly change the implementation of your service, moreover, Phpstorm can handle this very easily.
You will still be able to use app()->make() to obtain an instance of your service.
This, however, will not be automatically picked up by Phpstorm. But you can help it to understand that, all you need to do is to use #var annotation, see:
/**
* #var LoggerServiceInterface $logger
*/
$logger = app()->make(LoggerServiceInterface::class);
That way, Phpstorm will know what to expect from that $logger object.
I'm hard coding the $connection and $queue in over 10 files so I'm trying to clean that up. My first thought is to create some helpers that I can access in all of these files. However, I don't need those methods/variable available throughout my entire app. Instead, it would make most sense to place them in the ShouldQueue class. Any thoughts on the proper way to do this?
namespace App\Listeners\User;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
class CreateJenzUser implements ShouldQueue
{
use InteractsWithQueue;
public $connection = 'sqs_high';
public $queue = 'portal_high.fifo';
//Would rather use
public $connection = $highConnection;
public function handle(UserBeingCreated $event)
{
}
}
EDIT
It turns out Laravel is creating a new instance of CreateJenzUser without the constructor.
vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php - line 479
/**
* Create the listener and job for a queued listener.
*
* #param string $class
* #param string $method
* #param array $arguments
* #return array
*/
protected function createListenerAndJob($class, $method, $arguments)
{
$listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
return [$listener, $this->propagateListenerOptions(
$listener, new CallQueuedListener($class, $method, $arguments)
)];
}
Just need to figure out how to override this method.
I would make a parent abstract class and extend it from all the classes that needs to use the queue. You can specify all the method that you must implement as abstract methods in the parent class so you do not need to implement the ShouldQueue interface.
So, the parent class will look like
abstract class Queue
{
use InteractsWithQueue;
protected $connection = 'sqs_high';
protected $queue = 'portal_high.fifo';
// list here all the methods in your ShouldQueue interface
abstract protected function handle(UserBeingCreated $event);
}
Then the child class:
class CreateJenzUser extends Queue
{
protected function handle(UserBeingCreated $event)
{
// your code here
}
}
Update
If you need to keep the interface (for type hint check for example) you can extend and implement in the same time. So, the child class in this case will look like:
class CreateJenzUser extends Queue implements ShouldQueue
{
protected function handle(UserBeingCreated $event)
{
// your code here
}
}
The Queue class may not need to be abstract in this case. However, that depends on how you want to design. If you have some methods that you want to call from the parent but define in the child you can still keep it as an abstract class.
It sounds like that all those classes share code, that should be refactored. Probably the best option would be to extract class and then pass it as a dependency in the constructors of classes, which need that behavior.
You should also consider the fact, that trait is basically interpretator assisted copy-paste. You are just using a language structure to hide that code duplication.
P.S.
Don't use extends to fix this. The "extends" keyword should be read as "is a special subtype of". And user is not a special type of queue ... frankly, some could see it as an insult.