Laravel Observer Updating/Updated - laravel

I am not sure how best to do this. I need access to is_dirty() to check if specific fields were changed which needs to be done in the 'Updating' method in the observer. But I only want to act on it if the update is successful.
If there a way within the 'Updating' method to tell it to try to save the changes and then see if it was successful?
If not, what would be the best way to pair Updating and Updated? Would I just save a cache value in Updating specifying if the relevant fields were modified, then pull that cache value in Updated? Or would there be a better way?

You could try something along these lines:
class ModelObserver
{
public function updated(Model $model)
{
/**
* Listening inside 'updated' since we only care about model
* updated which have already happened
*/
if ($model->wasChanged('my-special-field')) {
// Do something...
}
}
}
You can read more about the wasChanged method here.

Related

Invalidate Shopware 6 page cache on entity update via API

We created a custom CMS element which displays entries which are managed via API.
Now when entries are updated and Shopware 6 runs ins production mode, the changes are not reflected on the page. I believe this is due to the page cache. (APP_ENV=prod)
What do we have to do to invalidate the cache automatically?
I checked the docs, but did not find the necessary information.
For the product box it works: When I place a product CMS element on the main page and change the product, the page is updated when I reload in the browser.
I was expecting to find some hint in \Shopware\Core\Content\Product\Cms\ProductBoxCmsElementResolver but there are no cache tags or something like this added there.
EDIT: Actually I was a bit inaccurate. The page I have lists the entities, and it is a category page.
So I believe I have too hook into CategoryRouteCacheTagsEvent.
For testing I hard-coded into:
\Shopware\Core\Content\Category\SalesChannel\CachedCategoryRoute::extractProductIds
$slots = $page->getElementsOfType('jobs');
/** #var CmsSlotEntity $slot */
foreach ($slots as $slot) {
$box = $slot->getData();
$ids = array_merge($ids, $box['jobs']->getIds());
}
But this does not yet work.
PS: Also I noticed some code duplication in the core in \Shopware\Core\Content\Category\SalesChannel\CachedCategoryRoute::extractProductIds and \Shopware\Core\Content\LandingPage\SalesChannel\CachedLandingPageRoute::extractIds
The Shopware\Core\Framework\Adapter\Cache\CacheInvalidationSubscriber listens to a lot of events, including indexer and entity-written events. This in turn uses the CacheInvalidator to invalidate cached data based on tags/cache keys.
You should be able to add invalidation based on your own entity in a similar fashion.
For this to work with a custom entity, you will probably have to tag any cache entries with something you can generate on invalidation. For CMS pages, I would probably start with the CachedLandingPageRoute as a reference.
I suggest you should have a look at the CacheInvalidationSubscriber and its service definition. You can see that there are already a bunch of events that are dispatched when write operations to certain entities occur. When you then look at the respective handler you can see how it invalidates the cache for whatever kind of routes it should affect.
When you speak of entries I assume you implemented your own custom entities for use in your CMS element? If that is the case just replicate the listener for your own entities. Otherwise you'll have to look for another event that is dispatched once you save your changes and then invalidate the cache likewise.
Based on the answers of dneustadt and Uwe, as for the job listings I solved it like with this two subscribes. I do not need any single ID here, because the full listing page has to be invalidated in case a job is deleted or added. This is why I went with the any-jobs tag:
use Shopware\Core\Content\Category\Event\CategoryRouteCacheTagsEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CacheKeyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
CategoryRouteCacheTagsEvent::class => 'generateTags'
];
}
public function generateTags(CategoryRouteCacheTagsEvent $event): void
{
$page = $event->getResponse()->getCategory()->getCmsPage();
$slots = $page->getElementsOfType('jobs');
if (!empty($slots)) {
$event->setTags(
array_merge($event->getTags(), ['any-jobs'])
);
}
}
}
and
class CacheInvalidationSubscriber implements EventSubscriberInterface
{
private CacheInvalidator $cacheInvalidator;
public static function getSubscribedEvents(): array
{
return [
EntityWrittenContainerEvent::class => 'invalidateJobs'
];
}
public function __construct(CacheInvalidator $cacheInvalidator)
{
$this->cacheInvalidator = $cacheInvalidator;
}
public function invalidateJobs(EntityWrittenContainerEvent $event): void
{
if (!empty($event->getPrimaryKeys(\ApplicationManagement\Core\Content\JobAd\JobAdDefinition::ENTITY_NAME))) {
$this->cacheInvalidator->invalidate(
['any-jobs']
);
}
}
}

Eloquent model getRawOriginal() returns new values in `updating` observer

I need to compare original model values with changes that are done when update occurs, but I am not able to do that.
My model update code looks like this:
public function update(array $user, int $userId): ?bool
{
return User::find($userId)->update($user);
}
I thought that the best way to capture changes is to use observer and observe updating event, because I assume it's called right before changes are stored in the database. Here is updating method code:
public function updating(User $user)
{
Log::info('Original user', ['original' => $user->getRawOriginal('status')]);
}
I've tried logging a bit and it seems that updating method gets called after the update happens and then when I try to retrieve original model values there it returns new ones, instead of the original ones.
If I use getChanges() method in updating it returns exactly what has changed, so it seems that changes are tracked somehow, but not the original values?
Can someone give me any pointers how to solve this and explain to me why my approach doesn't work?
Update: Code, where I call update, is wrapped with DB transaction methods. After removing them it seems that updating method gets called at the right time and getRawOriginal then returns expected results.
In this case, what are my options? Is there a way to do what I want without removing transactions?
You can add boot method in your model .Both updating and updated trigger while updating .
protected static function boot()
{
parent::boot();
static::updating(function ($model){
echo "updating";
dump($model->getDirty());
dump($model->getRawOriginal('username'));
});
static::updated(function ($model){
echo "updated";
dump($model->getDirty());
dump($model->getRawOriginal('mobile_number'));
});
}

How to override save() method in a component

Where and how I am overriding the save method in Joomla 3.0 custom component ?
Current situation:
Custom administrator component.
I have a list view that displays all people stored in table.
Clicking on one entry I get to the detailed view where a form is loaded and it's fields can be edited.
On save, the values are stored in the database. This all works fine.However, ....
When hitting save I wish to modify a field before storing it into the database. How do I override the save function and where? I have been searching this forum and googled quiet a bit to find ways to implement this. Anyone who give me a simple example or point me into the right direction ?
Thanks.
Just adding this for anyone who wants to know the answer to the question itself - this works if you explicitly wish to override the save function. However, look at the actual solution of how to manipulate values!
You override it in the controller, like this:
/**
* save a record (and redirect to main page)
* #return void
*/
function save()
{
$model = $this->getModel('hello');
if ($model->store()) {
$msg = JText::_( 'Greeting Saved!' );
} else {
$msg = JText::_( 'Error Saving Greeting' );
}
// Check the table in so it can be edited.... we are done with it anyway
$link = 'index.php?option=com_hello';
$this->setRedirect($link, $msg);
}
More details here: Joomla Docs - Adding Backend Actions
The prepareTable in the model (as mentioned above) is intended for that (prepare and sanitise the table prior to saving). In case you want to us the ID, though, you should consider using the postSaveHook in the controller:
protected function postSaveHook($model, $validData) {
$item = $model->getItem();
$itemid = $item->get('id');
}
The postSaveHook is called after save is done, thus allowing for newly inserted ID's to be used.
You can use the prepareTable function in the model file (administrator/components/yourComponent/models/yourComponent.php)
protected function prepareTable($table)
{
$table->fieldname = newvalue;
}

Doctrine 2 result cache invalidation

I'm using Doctrine 2's result cache on a query retrieving the number of new messages of a user (messaging app):
$query->useResultCache(true, 500, 'messaging.nb_new_messages.'.$userId);
I tried to invalidate this cache like this (in my entity repository):
public function clearNbNewMessagesOfUserCache($userId) {
$cacheDriver = $this->getEntityManager()->getConfiguration()->getResultCacheImpl();
$result = $cacheDriver->delete('skepin_messaging.nbNewMessages.'.$userId);
if (!$result) {
return false;
}
return $cacheDriver->flushAll();
}
So that I don't need to make a useless query on each page of my website.
My questions: is that a recommended practice? Will I eventually run into problems?
I had the idea to build an onFlush hook.
There you have all entities queued for inserts, updates and deletes hence you can invalidate the caches depending on entity name and identifier etc.
Unfortunately, I have not yet build any event listeners but I definitely plan to build such a thing for my project.
Here is a link to the doctrine documentation for the onFlush event
Edit:
There is even an easier way to implement events.
In an entity class you can add #HasLifecycleCallbacks to the annotations and than you can define a function with a #PreUpdate or #PrePersist annotation.
Than every time this model is updated or persisted this function will be called.
/**
* #Entity
* #Table(name="SomeEntity")
* #HasLifecycleCallbacks
*/
class SomeEntity
{
...
/**
* #PreUpdate
* #PrePersist
*/
public function preUpdate()
{
// This function is called every time this model gets updated
// or a new instance of this model gets persisted
// Somethink like this maybe...
// I have not yet completely thought through all this.
$cache->save(get_class($this) . '#' . $this->getId(), $this);
}
}
So maybe this can be used to invalidate every single instance of an entity?
This is an old question I stumbled upon. It's really simple using Doctrine 2.8 nowadays:
/** #var \Psr\Cache\CacheItemPoolInterface|null $cache */
$cache = $em->getConfiguration()->getResultCache();
$cache->deleteItem('skepin_messaging.nbNewMessages.'.$userId);
$cache->clear(); // clear all items
Please be aware that Doctrine internally generates a "real cache key" which won't look like yours. I don't know how to generate that cache key, without re-creating the used query.

How do order statuses/states work in Magento 1.4.x

As far as I understand Magento have various order statuses under global/sales/order/statuses which can be altered by either copying app/code/core/Mage/Sales/etc/config.xml to local scope or overriding it with your custom module.
There are also global/sales/order/states defined in the same file and as far as I understand states are something like statuses groups. Only states (not statuses) can be set at the order status in magento and statuses are something like states subdivisions. So in administrator interface you can change statuses of a placed order but you can't change state from the order status drop-down (you can change it by either invoicing the client or canceling the order).
As far as I understand you can easily add a new status to your Magento but can't add new state as states are somehow hardcoded with the rest or Magento order processing logic. I really hope that I'm wrong.
Please correct me if I'm wrong in any point as these are just my thoughts and it might be quite far from real Magento 1.4.x flow.
I'm quite sure that 'state' is free data, it can be set to ANY value using the setData option on an order instance. So if you write a custom module that can load an order, set data to one of your new 'states' and test with what ever custom logic you require.
$order = Mage::getModel('sales/order')->load(1);
$order->setData('state','myCustomState');
$order->setData('status','onCustomState');
echo $order->getState()
// Returns myCustomState
its worth bearing in mine that CLOSED/CANCELLED are protected states, so trying to use $order->setState('my_state') will fail on those order, but shouldn't fail on setData so you can overwrite a closed or cancelled order with this method.
if you were to overwrite the order model with a custom one you could add your own logic such as isOnCustomState() and allow the order to be handled in any way just by loading by id.
To add 'custom logic' to your order you could do something copy app\code\core\Mage\Sales\Model\Order.php into your local folder, then you can add functions into that model,
public function isActive(){ if($this->getState() == 'active'){ return true; } else { return false; }
public function isInActive(){ if($this->getState() == 'deactivated'){ return true; } else { return false; }
public function activate(){
if(!$this->isActive()){
$this->setData('state','active');
$this->setData('status','Active Subscription');
// some custom code to start a recuring payment
return $this;
}
}
public function deactiveate(){
if(!$this->isInActive()){
$this->setData('state','deactivated');
$this->sendOrderUpdateEmail(true,'Your subscription has been deactivated.');
// some custom code to stop a recuring payment
return $this;
}
}
now you can load an order and set activate or deactivate on that order to fire your custom code,
$order = Mage::getModel('sales/order')->load(1)->activate();
this code is all untested and just an example of somethings you could try, please don't just dump this code in your model and expect it to work perfectly. in code\core\Mage\Sales\etc\config.xml in the nodes sales/order/states add
<activated translate="label">
<label>Active Subscription</label>
<statuses>
<pending default="1"/>
</statuses>
<visible_on_front/>
</activated>
Magento Order is a finite state machine.
Therefore when you define status it would automatically try to define its state. Altering state directly is not recommended.
When you define status it checks various flags for shipment and invoices and depending in all those it would get the right state. Once order is in approved state however you can easily change status regardless of if there is invoice or any flag etc.

Resources