Reset Cache TTL in each Access - laravel

I was wondering if there is any way to update cache TTL from the last time it has accessed?
currently, I have a method to login to adobe connect with API call and API session is valid for 4 days from the last call.
but my cache driver only keeps session in the cache for 4 days from the moment that is added. but I want to keep it for 4 days since the last time it has accessed!
is there any way to update Cache TTL?
I'm sure forgetting and reinserting key is not best practice.
/**
* Login Client Based on information that introduced in environment/config file
*
* #param Client $client
*
* #return void
*/
private function loginClient(Client $client)
{
$config = $this->app["config"]->get("adobeConnect");
$session = Cache::store($config["session-cache"]["driver"])->remember(
$config['session-cache']['key'],
$config['session-cache']['expire'],
function () use ($config, $client) {
$client->login($config["user-name"], $config["password"]);
return $client->getSession();
});
$client->setSession($session);
}

You could listen for the event CacheHit, test for the key pattern, and reset the cache for that key with a new TTL.
To do that, you should create a new listener and add it to the EventServiceProvider:
protected $listen = [
'Illuminate\Cache\Events\CacheHit' => [
'App\Listeners\UpdateAdobeCache',
]
];
And the listener:
class UpdateAdobeCache {
public function handle(CacheHit $event)
{
if ($event->key === 'the_cache_key') { // you could also test for a match with regexp
Cache::store($config["session-cache"]["driver"])->put($event->key, $event->value, $newTTL);
}
}
}

Related

Shopware 6: Changing URL parameters returns the same result in prod mode

We extended the product listing to contain the specific paramter "property-groups" which influences the output.
All works fine in dev mode, but when switching to prod mode, changing that parameter does not have any effect any more.
It seems this is cached, but why is that, even the URL parameters change?
i.e.
/widgets/cms/navigation/67e6a05deae747e984a622c7e94e6881?no-aggregations=1&order=name-asc&p=1&property-groups=385b9ebbfc8e450bac9c4d63052ddf7f&slots=7b2547b253a145468fa35eb684b26a62
returns the same like
/widgets/cms/navigation/67e6a05deae747e984a622c7e94e6881?no-aggregations=1&order=name-asc&p=1&property-groups=&slots=7b2547b253a145468fa35eb684b26a62
If we flush the full cache, the second call works and returns the right result.
We of course do not want to fully disable the caching, so we probably have to tell Shopware 6 somehow, to use the new parameter in the cache key.
But we could not find, where the caching happens by debbuging through \Shopware\Storefront\Controller\CmsController::category.
Problem seems to be this:
\Shopware\Core\Content\Product\SalesChannel\Listing\CachedProductListingRoute::generateKey
private function generateKey(string $categoryId, Request $request, SalesChannelContext $context, Criteria $criteria): string
{
$parts = [
self::buildName($categoryId),
$this->generator->getCriteriaHash($criteria),
$this->generator->getSalesChannelContextHash($context),
];
$event = new ProductListingRouteCacheKeyEvent($parts, $categoryId, $request, $context, $criteria);
$this->dispatcher->dispatch($event);
return md5(JsonFieldSerializer::encodeJson($event->getParts()));
}
Here a cache key is generated which takes account the standard parameters, but not the new one added by us.
You have to listen for the ProductListingRouteCacheKeyEvent and add your parameter to the parts.
public static function getSubscribedEvents(): array
{
return [
// ...
ProductListingRouteCacheKeyEvent::class => 'addPropertyGroupsCachePart'
];
}
public function addPropertyGroupsCachePart(ProductListingRouteCacheKeyEvent $event)
{
$event->addPart((string)$event->getRequest()->get('poperty-groups'));
}
This way Shopware generates a unique cache key that is also based on that parameter we introduced and the cache collision disappears.

Laravel 5.7: prevent auto load injection in controller constructor

I have a HomeController with his constructor that takes a Guzzle instance.
/**
* Create a new controller instance.
*
* #param \GuzzleHttp\Client|null $client
*
* #return void
*/
public function __construct(Client $client = null)
{
$this->middleware('auth');
$this->middleware('user.settings');
if ($client === null) {
$param = [
'base_uri' => 'http://httpbin.org/',
'defaults' => [
'exceptions' => false,
'verify' => false
]
];
$client = new Client($param);
}
$this->setClient($client);
}
I would use via __constructor() to be able to mock it in tests.
My issues is that Laravel automatically auto-load the injection and the Guzzle Client injected has blank defaults (and cannot anymore edit it). In other words: at first call of HomeController Client is not null. And I need as null.
How can I stop this behaviour (only for the __construct() for HomeController)? I really use the DI in every part of my webapp.
EDIT
I just find that if I don't type-hints the Client, of course Laravel cannot auto-load. Is this the right mode to work?
New constructor:
public function __construct($client = null)
Thank you
I had a simular situation when testing apis. I ended up binding an instance of GuzzleClient to the service container (see documentation). Something like:
$this->app->instance('GuzzleHttp\Client', new MockClient);
To successfully mock the instance, I then checked to see whether or not it had a certain property value (in my case base_url being set). That determined whether or not the instance was a test as base_url would be set.
Along side this method, GuzzleHttp\Client does have a MockHandler you may want to explore. This can be used to fake response bodies, headers and status codes.

Drupal 8 - Add custom cache context

I have the following situation: I want to hide or show some local Tasks (Tabs) based on a field on the current user. Therefore I have implemented a hook_menu_local_tasks_alter() in my_module/my_module.module:
function my_module_menu_local_tasks_alter(&$data, $route_name, \Drupal\Core\Cache\RefinableCacheableDependencyInterface &$cacheability) {
... some logic ...
if ($user->get('field_my_field')->getValue() === 'some value')
unset($data['tabs'][0]['unwanted_tab_0']);
unset($data['tabs'][0]['unwanted_tab_1']);
... some logic ...
}
This works fine but I need to clear the caches if the value of field_my_field changes.
So I found that I need to implement a Cache Context like this in my my_module_menu_local_tasks_alter:
$cacheability
->addCacheTags([
'user.available_regions',
]);
I have defined my Cache Context like this:
my_module/my_module.services.yml:
services:
cache_context.user.available_regions:
class: Drupal\my_module\CacheContext\AvailableRegions
arguments: ['#current_user']
tags:
- { name: cache.context }
my_module/src/CacheCotext/AvailableRegions.php:
<?php
namespace Drupal\content_sharing\CacheContext;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
use Drupal\Core\Session\AccountProxyInterface;
/**
* Class AvailableRegions.
*/
class AvailableRegions implements CacheContextInterface {
protected $currentUser;
/**
* Constructs a new DefaultCacheContext object.
*/
public function __construct(AccountProxyInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* {#inheritdoc}
*/
public static function getLabel() {
return t('Available sub pages.');
}
/**
* {#inheritdoc}
*/
public function getContext() {
// Actual logic of context variation will lie here.
$field_published_sites = $this->get('field_published_sites')->getValue();
$sites = [];
foreach ($field_published_sites as $site) {
$sites[] = $site['target_id'];
}
return implode('|', $sites);
}
/**
* {#inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}
But every time I change the value of my field field_my_field I still need to clear the caches, so the Context is not working. Could anybody point me in the right direction how to get this solved or how to debug such kind of thigs?
Instead of providing a custom cache context, you should be able to use the default cacheability provided by core. I believe the issue is not so much the creation of the cacheable metadata, its seems that your hook_menu_local_tasks_alter is altering content that doesn't know it now relies on the user. So I believe you need 2 things:
General cache contexts that says 'this menu content now relies on the user', eg. user cache context.
Additional use of cache tag for a specific user to say: 'once this is cached, when this user entity changes, go regenerate the local tasks for this user'.
Note that HOOK_menu_local_tasks_alter provides a helper for cacheability, the third param of $cacheability. Drupal core also provides a mechanism here that allows us to say 'this piece of cache data relies on this other piece of cache data'.
Thus you should be able to do something like:
function my_module_menu_local_tasks_alter(&$data, $route_name, RefinableCacheableDependencyInterface &$cacheability) {
... some logic ...
// We are going to alter content by user.
$cacheability->addCacheableDependency($user);
// Note if you still really need your custom context, you could add it.
// Also note that any user.* contexts should already be covered above.
$cacheability->addCacheContexts(['some_custom_contexts']);
if ($user->get('field_my_field')->getValue() === 'some value')
unset($data['tabs'][0]['unwanted_tab_0']);
unset($data['tabs'][0]['unwanted_tab_1']);
... some logic ...
}

Deleting a File from Storage after Download

I'm working on a media asset management system. I want the user to be able to fill out a form with file width, height, extension and colorspace, then transform the image and serve it back as a download.
I can get that to work by responding to the Post-Request with the URL of the newly created file.
What I want is for that file to be deleted after download or after some time.
(Or, preferably, a way to use laravels download() Response, which I apparently can't use inside an Axios/Ajax post request).
Thanks for your help :)
There are two ways you can do this.
Let's assume you have a file in storage/app/download.zip:
1. Because Laravel uses Symfony's HttpFoundation internally, you can use the deleteFileAfterSend method:
public function download()
{
return response()
->download(storage_path('app/download.zip'))
->deleteFileAfterSend(true);
}
2. Create a Terminable Middleware that deletes the file after the download response was prepared.
class StorageDownload
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
\Storage::delete('download.zip');
}
}
You'll need to register the middlware and assign it to your route for it to work.
As for triggering the download using JavaScript, something as trivial as setting the window location will work:
axios
.post('files/export/' + file.id, formData)
.then(function() {
window.location = 'files/download/' + file.id
});
Don't worry, this will not navigate away from your current page, it will just trigger the download.
for me , i like to handle this case using cron
you can read details here
add any column to check it's been downloaded or not (e.g status with value 0 or 1)
and you can create accessor on your model to count the date diff
public function getRangeDateAttribute(){
$today = Carbon::now()->startOfDay();
$downloadDate = $this->created_at;
$datediff = $today->diffInDays($downloadDate);
return $datediff
}
then sample code inside task scheduling :
if($media->status == 1 || $media->rangeDate > 7 ){ //checking the status & range date passed specific days you wanted
do whatever you wanted here
}

MSSQL Driver Issue - Eloquent save tries to set identity column on update

Am getting this "Cannot update identity column 'id'" bug when I try to update using eloquent.
Am using Laravel 5.2
$m = new ModelName;
$m->name = 'Test model insert';
$m->save();
$m1 = ModelName::find( $m->id );
if ($m1) {
$m1->name = 'Test model update';
$m1->save(); // Error occured here.
}
You can use
trait WithoutUpdateId
{
/**
* Perform a model update operation.
*
* #param \Illuminate\Database\Eloquent\Builder $query
*
* #return bool
*/
protected function performUpdate(Builder $query)
{
// If the updating event returns false, we will cancel the update operation so
// developers can hook Validation systems into their models and cancel this
// operation if the model does not pass validation. Otherwise, we update.
if ($this->fireModelEvent('updating') === false) {
return false;
}
// First we need to create a fresh query instance and touch the creation and
// update timestamp on the model which are maintained by us for developer
// convenience. Then we will just continue saving the model instances.
if ($this->usesTimestamps()) {
$this->updateTimestamps();
}
// Once we have run the update operation, we will fire the "updated" event for
// this model instance. This will allow developers to hook into these after
// models are updated, giving them a chance to do any special processing.
$dirty = $this->getDirty();
if (count($dirty) > 0) {
unset($dirty['id']);
$this->setKeysForSaveQuery($query)->update($dirty);
$this->fireModelEvent('updated', false);
$this->syncChanges();
}
return true;
}
}
I have deleted id here
unset($dirty['id']);
In Model class
class ModelName extends Model
{
use WithoutUpdateId;
...
}
I figured my problem. I did unset $m1->id before calling $m1->save(). And it works great now.

Resources