how to use cache in cakePHP3 - caching

I am trying to use cache in cakePHP3 to store query results.
I declared a cache adapter named "bl"
config/app.php :
/**
* Configure the cache adapters.
*/
'Cache' => [
'default' => [
'className' => 'File',
'path' => CACHE,
'url' => env('CACHE_DEFAULT_URL', null),
],
'bl' => [
'className' => 'File',
'path' => CACHE . 'bl/',
'url' => env('CACHE_DEFAULT_URL', null),
'duration' => '+1 week',
],
src/Controller/UsersController.php :
use Cake\Cache\Cache;
...
public function test()
{
$this->autoRender = false;
$this->loadModel('Users');
$Users = $this->Users->find('all');
$Users->cache('test', 'bl');
debug(Cache::read('test', 'bl'));
}
The debug return "false".
tmp/cache/bl/ directory were well created, but no cache files were generated.
Am I missing something ?

Your query is never being executed, hence it's never going to be cached. Run the query by invoking all(), or toArray(), or by iterating over it, etc...
See also
Cookbook > Database Access & ORM > Query Builder > How Are Queries Lazily Evaluated

You are not calling the proper method, you need to use Cache::write() not Users->cache(). I updated your code below:
use Cake\Cache\Cache;
...
public function test()
{
$this->autoRender = false;
$this->loadModel('Users');
$Users = $this->Users->find('all');
Cache::write('cache_key_name', $Users, 'bl');
debug(Cache::read('cache_key_name', 'bl'));
}
See https://book.cakephp.org/3.0/en/core-libraries/caching.html#writing-to-a-cache

I was able to find the solution with your 2 answers, the final code is :
public function test()
{
$this->autoRender = false;
$users = $this->Users->find('all')->toArray();
Cache::write('test_cache', $users, 'bl');
debug(Cache::read('test_cache', 'bl'));
}

Related

laravel endpoint hide field

How can i hide some fields ?
i want to hide the file field
Eloquent :
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return ReportResource::collection($reports);
Model :
...
public function getFileSizeAttribute()
{
return Storage::disk('files')->size($this->attributes['file']);
}
....
ReportResource:
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file), <-- i want to hide the file field
'file_size' => $this->fileSize, <-- but always show file_size
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
to get file_size field i must select the file field , because it's depends on it to calculate the file size.
but i want to hide the file field before send the response.
i know i can use the protected $hidden = [] method in the model , but i don't want that, because file field it's required on others place. i just want to hide it on this endpoint only.
Since you are using API resources the best and clean way to do this is by using a Resource class for your collection.
Said that, you will have 3 Resources:
The first one, as it is, just for retrieving a single Model with file and file_size attributes. The one you already have ReportResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file' => $this->whenNotNull($this->file),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
A new second resource to be used in your endpoint, without the file attribute. IE: ReportIndexResource.php
...
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'year' => $this->year,
'views' => $this->whenNotNull($this->views),
'file_size' => $this->fileSize,
'created_at' => $this->created_at,
'company' => new CompanyResource($this->company),
];
}
Now you need to create a Resource collection which explicitly defines the Model Resource to use. IE: ReportCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ReportCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* #var string
*/
public $collects = ReportIndexResource::class;
}
Finally, use this new resource collection in your endpoint
$reports = Report::select('id', 'file','company_id', 'title', 'year', 'created_at')
->orderBy('id', 'desc')
->paginate(10);
return new ReportCollection($reports);
Of course, you can make use of makeHidden() method, but IMO is better to write a little more code and avoid a non desired attribute in your response because you forgot to make it hidden.
Also, in case you make use of makeHidden() method and you want to show the attribute in a future, you will have to update all your queries instead of a silgle resource file.
If you want to make it Hide From All Returns , you can Do this in model
protected $hidden = ['file'];
and if you want to do it temporirly with this query , you can Use MakeHidden method
$users = $reports->makeHidden(['file']);
It's clear in laravel docs , take a look
https://laravel.com/docs/9.x/eloquent-collections#method-makeHidden

How to edit picture on Coudinary with Laravel

Any Idea how to edit pictures on Cloudinary using Laravel API? I did a lot of searches, but I didn't find any references. The add worked successfully, but I didn't find a solution for the edit.
Add code
$user->user_image = Cloudinary::upload(
$request->file('user_image')->getRealPath(),
[
'folder' => 'Testing'
]
)->getSecurePath();
Attempt at updating picture
public function updatePicture(Request $request, $user_id)
{
$data = $request->validate([
'user_image' => '',
]);
$data = Cloudinary::upload(
$request->file('user_image')->getRealPath(),
[
'folder' => 'Testing'
]
)->getSecurePath();
User::where("user_id", $user_id)->update($data);
return response()->json([
'message' => 'Successfully updated Picture!',
'success' => true,
], 200);
}
For deleting, you can use the destroy() method of the API, for example:
$result = Cloudinary::destroy(
$public_id, //Public ID of the file from Cloudianry to delete - returned from the Upload API response
[
'...' => '...' //any optional parameters
]
)
For a list of all optional parameters and possible values please see:
https://cloudinary.com/documentation/image_upload_api_reference#destroy_method
In terms of updating, I am assuming you are referring to Cloudinary's update details of a single resource method of the Admin API. If so, you can access it like so:
$admin_api = Cloudinary::admin();
$result = $admin_api->update($public_id, $options = []);
Alternatively, if you're referring to the explicit method of the Upload API then you could access it like so:
$result = Cloudinary::explicit($public_id, $options = []);

Additional data in Laravel Resource

I use Laravel resource from the controller:
$data = Project::limit(100)->get();
return response()->json(ProjectResource::collection($data));
I like to pass additional information to the ProjectResource. How it's possible? And how can i access the additional data?
I tried like this:
$data = Project::limit(100)->get();
return response()->json(ProjectResource::collection($data)->additional(['some_id => 1']);
But it's not working.
What's the right way?
I like to access the some_id in the resource like this.
public function toArray($request)
{
return [
'user_id' => $this->id,
'full_name' => $this->full_name,
'project_id' => $this->additional->some_id
];
}
In your controller don't wrap return Resource in response()->json.
Just return ProjectResource.
So like:
$data = Project::limit(100)->get();
return ProjectResource::collection($data)->additional(['some_id => 1']);
Sorry for misunderstanding the question.
I don't think there is an option to pass additional data like this. So you will have to loop over the collection and add this somehow.
One option is to add to resources in AnonymousCollection. For example:
$projectResource = ProjectResource::collection($data);
$projectResource->map(function($i) { $i->some_id = 1; });
return $projectResource;
and then in ProjectResource:
return [
'user_id' => $this->id,
'full_name' => $this->full_name,
'project_id' => $this->when( property_exists($this,'some_id'), function() { return $this->some_id; } ),
];
Or add some_id to project collection befour passing it to ResourceCollection.

Yii2 PageCache invalidation

Is there any possibility to invalidate or delete PageCache for a particular action.
Consider this:
class SiteController extends Controller
{
public function behaviors()
{
return [
'pageCache' => [
'class' => PageCache::className(),
'duration' => Yii::$app->params['cacheTime'], // seconds
'variations' => [
Yii::$app->language,
Yii::$app->request->get('id'),
],
],
];
}
public function actionIndex( $id )
{
// action code
}
}
And now I want to remove/invalidate cache for
action en/site/index?id=1
Currently I am thinking to write some code in a console app but do not know how to achieve this.
EDIT1: I try to rebuild-invalidate cache manually for a specific action. The code can't relay on 'dependency' because it is almost impossible to implement for that action.
EDIT2: The task is to rebuild cache only for the specific action (page) leave other cache intact.
You can use TagDependency for more granular invalidation:
public function behaviors()
{
return [
'pageCache' => [
'class' => PageCache::className(),
'duration' => Yii::$app->params['cacheTime'], // seconds
'variations' => [
Yii::$app->language,
Yii::$app->request->get('id'),
],
'dependency' => new \yii\caching\TagDependency([
'tags' => [
Yii::$app->requestedRoute,
Yii::$app->request->get('id'),
],
]),
],
];
}
To invalidate cache:
TagDependency::invalidate(Yii::$app->cache, [
'site/index', // route of action
123, // ID of page
]);
If someone else needs ...
Yii2 does not provide a native function to invalidate the cache of a specific page, however there is the delete function of the cache component. It would however be necessary to know the generated key for the requested page but the function that generates this key is protected (calculateCacheKey ()). In this way, the best way would be to create your own class extending \yii\filters\PageCache.
'pageCache' => function () {
return new class extends \yii\filters\PageCache{
public function init(){
parent::init();
$this->except = ['index'];
$this->duration = Yii::$app->params['cacheTime'], // seconds;
$this->variations = [
Yii::$app->language,
Yii::$app->request->get('id'),
];
if(Yii::$app->request->get('IC') == 1)
Yii::$app->cache->delete($this->calculateCacheKey());
}
public function beforeCacheResponse(){
return Yii::$app->request->get('IC') != 1;
}
};
},
In the provided code, for simplicity, I am using an anonymous class (PHP 7).
Instead you can create your class as you wish and inform its path as the 'class' parameter, as seen in the configuration displayed in the question.
Note that I am using a simple logic to invalidate the cache, checking if there is a GET parameter IC == 1, you can use whatever logic you want.
If after invalidating the cache you do not want a new cache to be created, simply return false in beforeCacheResponse, it is from \yii\filters\PageCache.
You can invalidate the cache by using dependencies
'pageCache' => [
...
'dependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT COUNT(*) FROM post',
],
http://www.yiiframework.com/doc-2.0/yii-filters-pagecache.html#$dependency-detail
If I understand correctly you are trying to disable caching only for a specific action and according to the DOCS you can use the following options to explicitly identify which action IDs to apply the cache filter OR which action IDs it should not.
$except array List of action IDs that this filter should not apply to. yii\base\ActionFilter
$only array List of action IDs that this filter should apply to.
The following should work for you
return [
'pageCache' => [
'class' => PageCache::className(),
'except'=>['index']
'duration' => Yii::$app->params['cacheTime'], // seconds
'variations' => [
Yii::$app->language,
Yii::$app->request->get('id'),
],
],
];

yii2 Unable to verify your data submission

I use nginx, PHP 5.5.14, php-fpm, yii2, mac os.
I changed yii config to store session in database (postgress, user is superuser).
This is in my config:
'session' => [
'class' => 'yii\web\DbSession',
'sessionTable' => 'session',
],
And now when I try to register new user I've got this error:
Bad Request (#400)
Unable to verify your data submission
Here is a part of log:
10 17:25:57.434 info yii\db\Command::query SELECT "data" FROM "session" WHERE "expire">1425993957 AND "id"='cfg9sutufqchr1tdose4cack15'
/Users/pupadupa/Dev/www/mint-office-web/components/Controller.php (41)
11 17:25:57.442 info yii\web\Session::open Session started
/Users/pupadupa/Dev/www/mint-office-web/components/Controller.php (41)
12 17:25:57.450 error yii\web\HttpException:400 exception 'yii\web\BadRequestHttpException' with message 'Не удалось проверить переданные данные.' in /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/web/Controller.php:110
Stack trace:
#0 /Users/pupadupa/Dev/www/mint-office-web/components/Controller.php(41): yii\web\Controller->beforeAction(Object(app\controllers\user\RegistrationAction))
#1 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Controller.php(149): app\components\Controller->beforeAction(Object(app\controllers\user\RegistrationAction))
#2 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Module.php(455): yii\base\Controller->runAction('registration', Array)
#3 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/web/Application.php(83): yii\base\Module->runAction('user/registrati...', Array)
#4 /Users/pupadupa/Dev/www/mint-office-web/vendor/yiisoft/yii2/base/Application.php(375): yii\web\Application->handleRequest(Object(yii\web\Request))
#5 /Users/pupadupa/Dev/www/mint-office-web/web/index.php(20): yii\base\Application->run()
#6 {main}
13 17:25:57.454 trace yii\base\Controller::runAction Route to run: index/error
BTW,
I've got <?= Html::csrfMetaTags() ?> in head section and I have csrf input in my form. So it seems not the problem
I don't want to do public $enableCsrfValidation = false; because I think it's not solution but workaround.
How could I understand what cause this error?
As I mentioned earlier problem only appears when I store session in database.
Some additional information:
I could set and get variable from session. For example, I put it in beoreAction of Controller.php
Yii::$app->session->set('test', 'qwe');
$t = Yii::$app->session->get('test') ;
var_dump($t);
But after that If I comment first line like this
//Yii::$app->session->set('test', 'qwe');
$t = Yii::$app->session->get('test') ;
var_dump($t);
and refresh the page - I recieve NULL (BTW I could see Cookie:PHPSESSID=cfg9sutufqchr1tdose4cack15 in cookies after refresh).
So it seems there are some problems with session (DbSession) or maybe my php/php-fpm/nginx settings.
My UserController.php:
<?php
namespace app\controllers;
use app\components\Controller;
use app\models\Client;
use app\models\User;
use yii\filters\AccessControl;
use yii\web\BadRequestHttpException;
use yii\web\NotFoundHttpException;
class UserController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'employee'],
'rules' => [
[
'actions' => ['logout',],
'allow' => true,
'roles' => ['#'],
],
[
'actions' => ['employee', 'employee_add'],
'allow' => true,
'roles' => [ROLE_CLIENT_ADMIN],
],
],
],
];
}
public function actions()
{
return [
'login' => 'app\controllers\user\LoginAction',
'logout' => 'app\controllers\user\LogoutAction',
'restore' => 'app\controllers\user\RestoreAction',
'registration' => 'app\controllers\user\RegistrationAction',
'employee' => 'app\controllers\user\EmployeeAction',
'employee_add' => 'app\controllers\user\EmployeeAddAction',
//'passwd' => 'app\controllers\user\PasswdAction',
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV === 'dev' ? 'testme' : null,
],
];
}
/**
* #param int $clientId
* #return Client
* #throws BadRequestHttpException
* #throws NotFoundHttpException
*/
public function getClient($clientId)
{
if (!\Yii::$app->user->can(ROLE_ADMIN)) {
/* #var User $user */
$user = \Yii::$app->user->identity;
$clientId = $user->client_id;
}
if (!$clientId) {
throw new BadRequestHttpException('Bad request');
}
$client = Client::find()->where(['id' => $clientId])->one();
if (!$client) {
throw new NotFoundHttpException('Компания не найдена');
}
return $client;
}
}
My RegistrationAction.php:
<?php
namespace app\controllers\user;
use app\models\UserConfirm;
use Yii;
use app\components\Action;
use app\forms\RegistrationForm;
use yii\web\NotFoundHttpException;
class RegistrationAction extends Action
{
public function run($key = null)
{
if ($key !== null) {
/* #var UserConfirm $confirm */
$confirm = UserConfirm::find()->andWhere('expire > NOW()')->andWhere([
'key' => $key,
'action' => 'reg'
])->one();
if (!$confirm) {
throw new NotFoundHttpException('Key not found');
}
$user = $confirm->user;
$user->enabled = true;
$user->last_login = date('Y-m-d H:i:s');
$user->save();
$confirm->delete();
Yii::$app->user->login($user, 0);
return $this->controller->goHome();
}
$model = new RegistrationForm();
if ($model->load($_POST) && $model->validate() && $model->register()) {
$subject = Yii::$app->name . ' - Success';
$message = $this->controller->renderPartial(
'//email/registration',
[
'username' => $model->email,
'password' => $model->password,
'key' => $model->key,
'keyExpire' => $model->keyExpire
]
);
$res = Yii::$app->mailer->compose()
->setTo($model->email)
->setFrom([Yii::$app->params['from'] => Yii::$app->params['fromName']])
->setSubject($subject)
->setHtmlBody($message)
->send();
Yii::$app->session->setFlash('registrationFormSubmitted');
return $this->controller->refresh();
}
return $this->controller->render('registration', ['model' => $model]);
}
}
Looks like your session id changes. Check if the value of the session cookie changes after a second request. This happens when you misconfigure the domain that is set for the session cookie, make sure it matches the hostname in your URL.
I think this happens atimes when the server is messed up, all I needed to do was to restart my apache server.
For apache on MacOSx
sudo apachectl restart
For apache on Linux
sudo service apache2 restart
or
sudo service httpd restart
i hv used this while calling logout
$menuItems[] = [
'label' => 'Logout (' . Yii::$app->user->identity->username . ')',
'url' => ['/site/logout'],
// 'linkOptions' => ['data-method' => 'get']
'data-method' => 'get'
];
in main .php change post to get
---------------------------------------------------
and have changes this also
$url = Html::a('Logout',
['/site/logout'],
['class' => 'btn btn-success', 'data-method' => 'get']);
this is also in main.php
--------------------------------------------------------------------
in behaviour of site controller also
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'signup','index'],
'rules' => [
[
'actions' => ['signup'],
'allow' => true,
'roles' => ['?'],
],
[
'actions' => ['logout','index'],
'allow' => true,
'roles' => ['#'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['get'],
],
],
];
}
change post to get...and it will work....
What I did is, I just make the "csrfParam" different for both backend and frontend and after "php init" command everything is now working fine.
Not sure whether its a bug or we must have to set different "csrfParam" in case if we use DBsession.
Not sure if it's late, but I found this solution which worked for me in a similar case of Yii2 framework spitting 400 Bad Request at POST form requests.
Simply add this code in your form to manually include a CSRF input field for the key and Yii2 will accept the data submission:
<?php
use Yii;
echo Html::tag('input', '', ['type' => 'hidden', 'name' => '_csrf-backend', 'value' => Yii::$app->request->getCsrfToken()]);
?>
Note: this is for the advanced template. In the long run I suggest not to use a framework that bases on short tags as PHPers would know its a curse.

Resources