I have a table products and job_statuses using this package: laravel-job-status
There is a column in job_statuses called status the package made this column finished once the queue job is finished!
So I created a column in products table called job_status_id (relation with job_statuses) just to save the job status id in the products table to see if this job is finished!
Simply I created a component using Livewire for just refresh single product when the job finished refresh the component:
class ProductIndex extends Component
{
public $product;
public function mount($product)
{
$this->product = $product;
}
public function render()
{
return view('livewire.merchant.product.index');
}
}
Inside product-index component:
#if ($product->job_status->status == 'finished')
// show real image
#else
// show loader
#endif
My blade:
#foreach ($products as $product)
<livewire:merchant.product.product-index :product="$product" :key="$product->id">
#endforeach
How can refresh the product component if the status is finished?
You can add an event-listener in your component, then fire an event from anywhere on the page - even from JavaScript - to refresh the component.
To add a listener to make the component refresh itself, simply add the following line to your ProductIndex component.
protected $listeners = ['refreshProducts' => '$refresh'];
Livewire will now listen for any refreshProducts events that are emitted, and once they are emitted, it will refresh that component.
You can also customize it further, by replacing the magic $refresh action with a method, which you can pass parameters to. If you name the event the same as the method, you don't need to specify a key/value pair (eventname as the key, methodname as the value), and the value alone will suffice. Here's an example,
protected $listeners = ['refreshProducts'];
// ...
public function refreshProducts($product_id = null)
{
// Refresh if the argument is NULL or is the product ID
if ($product_id === null || $product_id === $this->product->id) {
// Do something here that will refresh the event
}
}
From within a Livewire component, you can emit events using
$this->emit('refreshProducts');`
// or if you are passing arguments, as with the second example,
$this->emit('refreshProducts', $productID);
You can also emit events from JavaScript, by doing
<script>
Livewire.emit('refreshProducts')
</script>
If you want to make the queue trigger that event once its complete, you need to implement something that either polls the server to ask "has the job finished" and then fire the event, or you can use Laravel Echo as a websocket. This will allow you to fire and listen for events from outside the Livewire ecosystem.
Polling
When you're polling, you don't have to emit an event for every update, as the component will refresh itself continuously .
Polling is the easiest way to continuously update a Livewire component, it does not require any websockets integration like Laravel Echo. This means that every X seconds (default is 2 seconds), your component is going to make an AJAX request to your server, to fetch its newest data, and re-render itself with the new data.
This is easily achieved by wrapping the component with the wire:poll attribute - here's an example of using 5 seconds.
<div wire:poll.5s>
<!-- The rest of your view here -->
</div>
However, this means that all instances of that component will re-render themselves and fire an AJAX request of their own to the server to get the newest data. You might want to make a "parent" component for all your items, thereby just having 1 singular component re-render.
Broadcasting & Websockets
I'm going to assume that you have installed Laravel Echo already. Now - to achieve this functionality of broadcasting, you first need to create an event. Run the command
php artisan make:event ProductStatusUpdated
This will create an event in your app\Events folder. Customize it to however you need it. It will look something like this after running the command,
class ProductStatusUpdated
{
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 PrivateChannel('channel-name');
}
}
We can change the channel from PrivateChannel('channel-name') to Channel('products'), this allows us to listen for it in Livewire in the products channel (you could name the channel whatever you want, and you can also listen for private events - there's documentation for that in the Livewire documentation, referenced at the bottom of this answer).
So that means the broadcastOn would look something like this
public function broadcastOn()
{
return new Channel('products');
}
Next, after the job has completed its work and all the statuses has been set, fire that event from Laravel, using
event(new \App\Events\ProductStatusUpdated);
Now we need to update our listener in the Livewire component, so that we can actually listen for the broadcast on that channel through Laravel Echo (and not the Livewire events that we did before).
protected $listeners = ['echo:products,ProductStatusUpdated' => 'refreshProducts'];
And we're done! You're now using Laravel Echo to broadcast an event, which Livewire intercepts, and then runs. The listener is now a key/value pair, where the value is still the method name in the component (you can still use the magic action $refresh instead if you desire to), and the key is channel,event prefixed by echo:.
Resources:
https://laravel-livewire.com/docs/2.x/events
https://laravel-livewire.com/docs/2.x/laravel-echo
Related
I have an Event with a bunch of queued listeners. I Can't run sync because I am calling external APIs etc
Events\Invoice\InvoiceEvent::class => [
Listeners\Invoice\Listener1::class, // should queue
Listeners\Invoice\Listener2::class, // should queue
Listeners\Invoice\Listener3::class, // Should NOT queue......
Listeners\Invoice\Listener4::class, // should queue
Listeners\Invoice\Listener5::class, // should queue
],
Calling this event from a controller method.
public function store(Request $request)
{
$invoice = Invoice::findOrFail($request->id);
InvoiceEvent::dispatch($invoice); // Async event, it cannot be sync
return $invoice; // need to return only when Listener3 finish execution
}
return $invoice is dependent on Listener3, otherwise, it will return incomplete data.
How can I return only when Listener3 is finished executing?
I came up with sleep(10); but it's not an ideal solution.
Listener3 saves data from third-party API to the invoices table which needs to be returned, that's why cannot return incomplete invoice data, right now the required data gets added to the invoice but after its return
PHP is natively synchronous. Unless you're pushing those events or listeners to a queue, (i.e. class Listener3 implements ShouldQueue) then they should run in order. However, you might want to rethink the structure of your code.
Listeners are best as reactions to an event, i.e. side effects, running independently from the rest of your application. Jobs, Events and Listeners should not generally return a value (except to halt a series of listeners). In your case, the Invoice is going through multiple steps, including calling a 3rd party API. Ideas:
Create a service class that performs the tasks on the Invoice and returns the invoice to the controller when completed (which then will return the $invoice data to the front end)
If you want the process to be async, consider using a push notification. Dispatch a job which performs the tasks on the Invoice, then alert the front end (e.g. Pusher ) when the invoice is ready to fetch.
There are a way to broadcast your event not queuing it,
into your event class add:
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
so your class declaration must implements ShouldBroadcastNow
class NotQueueEvent implements ShouldBroadcastNow { ... }
this spread event without enqueue.
If you want to wait to this method return, you should only don't put it on the queue. Run the event and await the return. Don't know if I understood correctly the problem.
I need to be able to write a Plugin that gets the orders, product, etc., whenever a new Order, Product is created in DrupalCommerce 2X. but I can't seem to figure out how Commerce wants me to do it. I don't see any *events files that would give me the data.
It looks like Commerce wants me to create a separate Event Flow plugin that would add the step I want, but I can't seem to find documentation about implementing my own Event Flow.
Can you guide me to the right path of running my code when the order or product is created? Am I on the right path? Can you point to Events/EventSubscriber Flow development docs?
On order complete, system call the commerce_order.place.post_transition. so You need to create an Event on Checkout complete.
Reacting to Transitions
Example - reacting to the order 'place' transition.
// mymodule/src/EventSubscriber/MyModuleEventSubscriber.php
namespace Drupal\my_module\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\state_machine\Event\WorkflowTransitionEvent;
class MyModuleEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
// The format for adding a state machine event to subscribe to is:
// {group}.{transition key}.pre_transition or {group}.{transition key}.post_transition
// depending on when you want to react.
$events = ['commerce_order.place.post_transition' => 'onOrderPlace'];
return $events;
}
public function onOrderPlace(WorkflowTransitionEvent $event) {
// #todo Write code that will run when the subscribed event fires.
}
}
Telling Drupal About Your Event Subscriber
Your event subscriber should be added to {module}.services.yml in the base directory of your module.
The following would register the event subscriber in the previous section:
# mymodule.services.yml
services:
my_module_event_subscriber:
class: '\Drupal\my_module\EventSubscriber\MyModuleEventSubscriber'
tags:
- { name: 'event_subscriber' }
For more reference review the following URL:
https://docs.drupalcommerce.org/commerce2/developer-guide/orders/react-to-workflow-transitions#reacting-to-transitions
The app I am working on fires an event, when one of the Eloquent model attributes is updated.
The Eloquent model is called Job and it is supposed to fire JobCustomerTotalAmountDueChanged when the duration attribute is updated. I have the following code in the JobObserver:
public function saved(Job $job)
{
if ($job->isDirty('duration')) {
event(new JobCustomerTotalAmountDueChanged($job));
}
}
When I try to test it using Event::fake, Eloquent events are not being fired, which means that the code in saved method is never execured. From what I see the assertDispatched and assertNotDispatched methods are only available for faked events. Is there a way to assert that a particular event is/is not fired without Event::fake?
The solution turned out to be very easy after all:
Laravel lets you specify which events should be faked, as an argument to fake method. In my example:
Event::fake([JobCustomerTotalAmountDueChanged::class])
All of the other events are being triggered and handled. Also, you can make assertions only with reference to events passed as argument to fake method. If you don't pass any argument, Laravel will try to 'fake' every event in the app.
Maybe it's a trivial question but I'm beginner with Silex and I don't know how to do it...so here is my situation :
I have a Silex app, everything works but I'm not a big fan of my Controller because there is a lot of code and I reapeat this code two times :
First controller :
$app->match('/', function (Request $request) use ($app) {
// 1/ I get data from a form
// 2/ I check if User is logged
// 2.1 / If not logged -> I created new User (Entity User) + send email
// 3/ I create a new Event (Entity Event) + put some file on AWS s3
// 4/ I create a new Product (Entity Product)
// 5/ I activate some add-on to my Event (request to an other BDD)
return $app['twig']->render('home.html.twig');
})->bind('home');
Second controller
$app->match('/manage-my-product', function (Request $request) use ($app) {
if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) {
// 1/ I get data from a form
// 2/ If user is here he must be logged so no need to check one more time
// 3/ I create a new Event (Entity Event) + put some file on AWS s3
// 4/ I create a new Product (Entity Product)
// 5/ I activate some add-on to my Event (request to an other BDD)
} else {
return $app->redirect($app["url_generator"]->generate("login"));
}
})->bind('product');
As you can see I do almost the same with few difference, so my question : Is to possible to put that logic outside the Controller and use it twice? How should I put that logic outside my Controller?
I don't really know how to do it because I have lot of different things inside :
I use 3 Entity that use 1 database connection (User, Event, Product)
Those Entities works the same : I have one Class Name with the attributes + getter / setter, one Class NameDAO where I have all my methods (findByEmail($email), find($id), ...) that extends an abstract DAO class (my database connection + one abstract method to return a Class Name)
Each Class NameDAO have a dedicated service (eg. $app['dao.user'])
I use an other database connection to activate the add-on
I use AWS s3 Class to put file on a server
EDIT :
According to the anwser I get I think my question wasn't very clear about my real problem...I had the intuition I need to use Service but I have trouble to build it so :
Should I build ONE big Service that uses multiple services?
OR
Should I build one service by question and repeat my logic in both my Controller?
As I said before, I already have services $app['user.dao'] and $app['event.dao'] for example that I use to create a new User or a new Event for example, so should I do :
$app->match('/', function (Request $request) use ($app) {
// 1/ Service 1 to create new user
// 2/ Service 2 to create new event
// 3/ Service 3 to create new add-on
// and so on...
return $app['twig']->render('home.html.twig');
})->bind('home');
OR
$app->match('/', function (Request $request) use ($app) {
// Use ONE big service that use all the services I need
return $app['twig']->render('home.html.twig');
})->bind('home');
END EDIT
As I said it works right now, but I'd like to know if it's possible to mix all this inside one method / function / service that I could use multiple time, could be nice for my future app !
Thanks
You can use Services which you add to the Service Container as documented here:
https://symfony.com/doc/current/service_container.html#creating-configuring-services-in-the-container
(check the Symfony version that you are using)
I'm using Doctrine to save user data and I want to have a last modification field.
Here is the pseudo-code for how I would like to save the form once the user presses Save:
start transaction
do a lot of things, possibly querying the database, possibly not
if anything will be changed by this transaction
modify a last updated field
commit transaction
The problematic part is if anything will be changed by this transaction. Can Doctrine give me such information?
How can I tell if entities have changed in the current transaction?
edit
Just to clear things up, I'm trying to modify a field called lastUpdated in an entity called User if any entity (including but not limited to User) will be changed once the currect transaction is commited. In other words, if I start a transaction and modify the field called nbCars of an entity called Garage, I wish to update the lastUpdated field of the User entity even though that entity hasn't been modified.
This is a necessary reply that aims at correcting what #ColinMorelli posted (since flushing within an lifecycle event listener is disallowed - yes, there's one location in the docs that says otherwise, but we'll get rid of that, so please don't do it!).
You can simply listen to onFlush with a listener like following:
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
class UpdateUserEventSubscriber implements EventSubscriber
{
protected $user;
public function __construct(User $user)
{
// assuming the user is managed here
$this->user = $user;
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
// before you ask, `(bool) array()` with empty array is `false`
if (
$uow->getScheduledEntityInsertions()
|| $uow->getScheduledEntityUpdates()
|| $uow->getScheduledEntityDeletions()
|| $uow->getScheduledCollectionUpdates()
|| $uow->getScheduledCollectionDeletions()
) {
// update the user here
$this->user->setLastModifiedDate(new DateTime());
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata(get_class($this->user)),
$this->user
);
}
}
public function getSubscribedEvents()
{
return array(Events::onFlush);
}
}
This will apply the change to the configured User object only if the UnitOfWork contains changes to be committed to the DB (an unit of work is actually what you could probably define as an application level state transaction).
You can register this subscriber with the ORM at any time by calling
$user = $entityManager->find('User', 123);
$eventManager = $entityManager->getEventManager();
$subscriber = new UpdateUserEventSubscriber($user);
$eventManager->addEventSubscriber($subscriber);
Sorry for giving you the wrong answer at first, this should guide you in the right direction (note that it's not perfect).
You'll need to implement two events. One which listens to the OnFlush event, and acts like this:
// This should listen to OnFlush events
public function updateLastModifiedTime(OnFlushEventArgs $event) {
$entity = $event->getEntity();
$entityManager = $event->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
if (count($unitOfWork->getScheduledEntityInsertions()) > 0 || count($unitOfWork->getScheduledEntityUpdates()) > 0) {
// update the user here
$this->user->setLastModifiedDate(new \DateTime());
}
}
We need to wait for the OnFlush event, because this is the only opportunity for us to get access to all of the work that is going to be done. Note, I didn't include it above, but there is also $unitOfWork->getScheduledEntityDeletions() as well, if you want to track that.
Next, you need another final event listener which listens to the PostFlush event, and looks like this:
// This should listen to PostFlush events
public function writeLastUserUpdate(PostFlushEventArgs $event) {
$entityManager = $event->getEntityManager();
$entityManager->persist($this->user);
$entityManager->flush($this->user);
}
Once the transaction has been started, it's too late, unfortunately, to get doctrine to save another entity. Because of that, we can make the update to the field of the User object in the OnFlush handler, but we can't actually save it there. (You can probably find a way to do this, but it's not supported by Doctrine and would have to use some protected APIs of the UnitOfWork).
Once the transaction completes, however, you can immediately execute another quick transaction to update the datetime on the user. Yes, this does have the unfortunate side-effect of not executing in a single transaction.
#PreUpdate event won't be invoked if there's no change on the entity.
My guess would have been, similarly to the other 2 answers, is to say whatever you want to do when there will or will not be changes, use event listeners.
But if you only want to know before the transaction starts, you can use Doctrine_Record::getModified() (link).