I am using file cache in yii2 framework.
My question is
Is it possible to add some extra value to cache without refresh the cacheFile.Suppose i create cache file for my products now on each entry i update cache file. I want to add just the new product to cache.
How can i do that thanks in advance
This is my Code
public static function updateCache(){
$product_grid = Yii::$app->db->createCommand("CALL get_products()")->queryAll();
Yii::$app->cache->set('product_grid', $product_grid);
}
I write store procedure for getting all products,now when i add new product each time i am calling the updateCache function which regenerate the products and add it to cache due to which application speed may be effected.
This is the code for addingProduct and updateCache:
public function actionCreate($id = NULL) {
$model = new PrProducts();
if ($model->load(Yii::$app->request->post())) {
$model->save(false);
self::updateCache();
}
}
Native Yii2 cache components doesn't allow to update existing cache items partially.
But you can do this manually:
public static function addToCache($modelProduct) {
$productGrid = Yii::$app->cache->get('productGrid');
$productGrid[$modelProduct->id] = $modelProduct->attributes;
Yii::$app->cache->set('productGrid', $productGrid);
}
But I recommend other way: you can store each product record as separate cache item.
Firstly you can add multiple items:
public static function refreshProductCache() {
// Retrieve the all products
$products = Yii::$app->db->createCommand("CALL get_products()")->queryAll();
// Prepare for storing to cache
$productsToCache = [];
foreach ($products as $product) {
$productId = $product['id'];
$productsToCache['product_' . $productId] = $product;
}
// Store to cache (existing values will be replaced)
Yii::$app->cache->multiSet($productsToCache);
}
Secondly you can update cache when you read data. For instance:
public function actionView($id) {
$model = Yii::$app->cache->getOrSet('product_'.$id, function() use ($id) {
return PrProducts::find()
->andWhere(['id' => $id])
->one();
});
return $this->render('view', ['model' => $model]);
}
This code creates cache only one time for each $id that not yet present in the cache.
Thirdly you can add individual products to cache right after create/update. For instance:
public static function addToCache(PrProducts $modelProduct) {
$productId = $modelProduct->id;
Yii::$app->cache->set('product_' . $productId, $modelProduct);
}
I think this approach more flexible. Of course, it may be less efficient than you way. It very depends from code that reads your cache.
Related
How can I add delay to laravel observer before it execute function?
I have this code but it doesn't work:
public function created(School $school)
{
$addTime = Carbon::parse($school->created_at)
->addSeconds(6);
if(Carbon::now() = $addTime) {
$manager = School::where('id', $school->id)->with('manager')->first();
$user = User::where('id', $school->manager->id)->first();
Mail::to($user->email)->locale('id')->queue(new SchoolGenerated($school, $user));
}
}
Logic
I have such function in my controller
public function store(Request $request) {
$school = new School;
//.....
if($school->save()){
//save manager.....
}
}
As you see I assign manager to school after school data been saved therefore if I run my observe immediately It won't find my user (manager) so I need to put delay into my observer till manager data stored as well.
How can I add delay to my observer?
you can use sleep(int $seconds ) function in php, try this:
public function created(School $school)
{
// add delay
$seconds = 10;
sleep($seconds);
$manager = School::where('id', $school->id)->with('manager')->first();
$user = User::where('id', $school->manager->id)->first();
Mail::to($user->email)->locale('id')->queue(new SchoolGenerated($school, $user));
}
}
Solved
As there was no way for me to get school data after manager being created so I've changed my observe to rule on manager creation instead of school creation that way I can get manager data and based on manager data get school data. So I just basically rotate the logic 180˚ and got my desire result.
I'm using Laravel 5.7, and I'm saving settings in the database.
public function store(Request $request)
{
$data = $request->only('app_name', 'app_desc', 'company_phone', 'company_address',
'service_wage', 'kavenegar_api', 'kavenegar_number'
);
foreach ($data as $key => $value) {
Setting::updateOrInsert(
['name' => $key],
['val' => $value]
);
}
return redirect()->back()->withFlashSuccess("saved");
}
I can get settings with the following helper.
if (!function_exists('getSetting')) {
function getSetting($key)
{
return Setting::where('name', $key)->value('val');
}
}
But there is a problem; We are listing all settings and calling getSetting('setting_name') multiple times which is making one query to the database for each call. That’s a lot of queries to get the settings.
I want to use the cache but how can I when the settings are stored in the database? I also save them in the cache.
It's pretty straightforward. Consult the docs on caching for details, but the basic idea is this:
function getSetting($key)
{
return Cache::remember('setting:' . $key, 3600, function() use($key) {
return Setting::where('name',$key)->value('val');
});
}
You could also cache all the settings into a single cache value. Depending on how many of the settings there are and what proportion you use in an average request, that may or may not be a good approach.
try this
if (!function_exists('getSetting'))
{
function getSetting($key)
{
return Setting::where('name', $key)->first()->val;
}
}
And Dont forget to add the Namespace App\Setting
How does the function Work
For Example if you have the setting named as app_name and While You pass the value to the function
Setting::where('name', $key)->first()->val;
getSetting('app_name'); it will find the first record with the key app_name and select the val field value from the object and returns it
UPDATED FOR THE CACHE
THIS IS UNTESTED FUNCTION
function getSetting($name)
{
if (Cache::has('setting_'.$name)) {
return Cache::get('setting_'.$name);
}
$query = Setting::where('name', $key)->first();
Cache::forever('setting_'.$name, $query->val);
return $query->val;
}
This Will remember the cache forever and visit the documentation to read about the cache
https://laravel.com/docs/5.8/cache
And dont forget to add the use Illuminate\Support\Facades\Cache; in the namespace list
I've developed an plugin where i make use of shortcodes. One of these shortcodes retrieves external information through communicate with an API.
The function that's called by the shortcode, communicates several times with the API. How could i cache all separate calls.
Let me try to explain it with some code
Example code
public function generateData($atts, $content = null, $tag = null){
// $this->API is a extended class to retrieve the API data
// example one calling the api
$data1 = $this->API->getData(array(
'teamlevel' => 'beginner'
));
$data2 = $this->API->getData(array(
'competition' => 'baseball'
));
}
add_shortcode('getdata', 'generateData');
class API {
public function getData($params){
// communicate with the api en return the data
}
}
Above code is working but without caching. Now i've tried to make use of wordpress get_transient and set_transient on the following way
public function generateData($atts, $content = null, $tag = null){
// $this->API is a extended class to retrieve the API data
// example one calling the api
// new chaining method cache
// every data call could be cached, i thought this could be based on shortcode name ($tag)
// set the shortcode name
$this->API->cacheShortcodeName = $tag; // getdata
$data1 = $this->API->cache()->getData(array(
'teamlevel' => 'beginner'
));
$data2 = $this->API->cache()->getData(array(
'competition' => 'baseball'
));
}
add_shortcode('getdata', 'generateData');
class API {
private $cacheDuration;
protected $cacheShortcodeName;
public function getData($params){
// before communication with the api check if there is cache with hasCache
// 1. $this->hasCache
// if result is true return cached data
// 2. return cached data
// else communicatie with the API en set new cache data
// 3. retrieve data from api en set new cached data
}
// default cache duration (1 day) in seconds
public function cache($cacheDuration = 86400){
$this->cacheDuration = $cacheDuration;
return $this;
}
public function hasCache(){
// check if there is cache
$cache = get_transient(md5($this->cacheShortcodeName));
if(!empty($cache)){
return true;
}
return false;
}
public function setCache($data){
set_transient(md5($this->cacheShortcodeName), $data, $this->cacheDuration);
}
public function getCache(){
$cache = get_transient(md5($this->cacheShortcodeName));
return $cache;
}
}
But the problem is when the cache is saved within the function setCache each time the shortcode name is set, and therefore i think its overriding itself. How could i store every call separately?
I want to implement a system in my project that "alerts" users when there is a new comment on one of their posts.
I currently query all comments on the posts from the logged in user and put everything in an array and send it to my view.
Now my goal is to make an alert icon or something when there is a new item in this array. It doesn't have to be live with ajax just on page load is already good :)
So I've made a function in my UsersController where I get the comments here's my code
public function getProfileNotifications()
{
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
//comments
if (!empty($projects)) {
foreach ($projects as $project) {
$comments_collection[] = $project->comments;
}
}
if (!empty($comments_collection)) {
$comments = array_collapse($comments_collection);
foreach($comments as $com)
{
if ($com->from_user != Auth::user()->id) {
$ofdate = $com->created_at;
$commentdate = date("d M", strtotime($ofdate));
$comarr[] = array(
'date' => $ofdate,
$commentdate,User::find($com->from_user)->name,
User::find($com->from_user)->email,
Project::find($com->on_projects)->title,
$com->on_projects,
$com->body,
Project::find($com->on_projects)->file_name,
User::find($com->from_user)->file_name
);
}
}
} else {
$comarr = "";
}
}
Is there a way I can check on page load if there are new items in the array? Like keep a count and then do a new count and subtract the previous count from the new one?
Is this even a good way to apprach this?
Many thanks in advance! Any help is appreciated.
EDIT
so I added a field unread to my table and I try to count the number of unreads in my comments array like this:
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
//comments
if (!empty($projects)) {
foreach ($projects as $project) {
$comments_collection[] = $project->comments;
}
}
$unreads = $comments_collection->where('unread', 1);
dd($unreads->count());
But i get this error:
Call to a member function where() on array
Anyone any idea how I can fix this?
The "standard" way of doing this is to track whether the comment owner has "read" the comment. You can do that fairly easily by adding a "unread" (or something equivalent) flag.
When you build your models, you should define all their relationships so that stuff like this becomes relatively easy.
If you do not have relationships, you need to define something like the following:
In User
public function projects()
{
return $this->hasMany('App\Models\Project');
}
In Project
public function comments()
{
return $this->hasMany('App\Models\Comment');
}
Once you hav ethose relationshipt, you can do the following. Add filtering as you see fit.
$count = $user->projects()
->comments()
->where('unread', true)
->count();
This is then the number you display to the user. When they perform an action you think means they've acknowledged the comment, you dispatch an asynchronous request to mark the comment as read. A REST-ish way to do this might look something like the following:
Javascript, using JQuery:
jQuery.ajax( '/users/{userId}/projects/{projectId}/comments/{commentId}', {
method: 'patch'
dataType: 'json',
data: {
'unread': false
}
})
PHP, in patch method:
$comment = Comment::find($commentId);
$comment->update($patchData);
Keep in mind you can use Laravel's RESTful Resource Controllers to provide this behavior.
try this
$unreads = $project->comments()->where('unread', 1);
dd($unreads->count());
EDIT
My be Has Many Through relation will fit your needs
User.php
public function comments()
{
return $this->hasManyTrough('App\Project', 'App\Comment');
}
Project.php
public function comments()
{
return $this->hasMany('App\Comment');
}
then you can access comments from user directly
$user->comments()->where('unread', 1)->count();
or I recommend you define hasUnreadComments method in User
public function hasUnreadComments()
{
$return (bool) $this->comments()->where('unread', 1)->count();
}
P.S.
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
this code is horrible, this way much better
$projects = Auth::user()->projects;
I have a DataObject class called AdminUpload that stores two variables: UploadDate (which is always going to bet set to the current date) and Total, which is an int.
The function updateUploads() is called and stores the current date and increments the Total by 1 each time its called.
<?php
class AdminUpload extends DataObject {
private static $db = array(
'UploadDate' => 'Date',
'Total' => 'int'
);
// Tell the datagrid what fields to show in the table
private static $summary_fields = array(
'ID' => 'ID',
'UploadDate' => 'Current Date',
'Total' => 'Version Number'
);
public function updateUploads(){
$uploadDate = SS_Datetime::now();
$this->UploadDate = $uploadDate;
$this->Total++;//increment the value currently stored in the database each time
$this->write();
}
}
What I want to to is, when someone uploads a new image in the admin view, then the updateCache() function is called during the onAfterWrite() process. I only want to maintain one entry in the database, though, so every time I upload an image, I want to have just one entry in the AdminUpload database table.
public function onAfterWrite(){
$updateGallery = parent::onAfterWrite();
$adminUploading = AdminUpload::get();
$adminUploading -> updateUploads();
return $updateGallery;
}
I've never tried to do a function call in SilverStripe like this--it seems simple enough but since I am not going to add a new entry to the database with each call to the updateUploads() function, that's where I'm stuck. Any tips would be helpful...
It is incorrect approach to create a whole table for just one record. If you were going to use theses two fields on a page, then adding them to that page (create a new page type) would be a better idea.
If you are talking about file uploads, then you can always query this information directly from database.
$uploadedFilesCount = File::get()->count();
$lastUploadedFileDate = File::get()->sort('CreatedDate', 'DESC')->first()->CreatedDate;
onAfterWrite is a hook and used from DataExtension. There are cases when hooks are called directly on DO and then on extensions.
Your extension code might look like this to handle 'onCreated' state:
class UploadsCounter extends DataExtension {
protected $isCreating = false;
public function onBeforeWrite() {
if (!$this->owner->isInDB()) {
$this->isCreating = true;
}
}
// called on validation or database error
public function onAfterSkippedWrite() {
$this->isCreating = false;
}
public function onAfterWrite() {
if (!$this->isCreating) return;
$this->isCreating = false;
$adminUploading = AdminUpload::get()->first();
if (!$adminUploading ) {
$adminUploading = new AdminUpload();
$adminUploading->write();
}
$adminUploading->updateUploads();
}
}
You should define UploadsCounter extension on the dataobject that you are going to count, for example:
mysite/_config/config.yml
File:
extensions:
- UploadsCounter