Yii2: can I $this->render(..) and then execute sql command? - performance

I have an idea how speed up page load. I have sql command that I need to execute in controller. What if I try to execute after rendering page? Rendering vars (model) does not depend on it.
public function actionIndex()
{
$model = new Model();
...
$this->render('index', [
'model' => $model,
]);
Yii::$app->db->createCommand('UPDATE ...')->execute();
return;
}

You could use "afterRender" to do some stuff if your really want to execute something after render.
http://www.yiiframework.com/doc-2.0/yii-base-view.html#afterRender%28%29-detail
$this->view->on('afterRender', ...);
or globally set in app/config/main.php
return [
'components' => [
'view' => [
'on afterRender' => function ($event) {
/** #var $event yii\base\ViewEvent */
},
],
],
];

In yii2 the the result of the render is returned to to the caller function..
this way
return $this->render('index', [
'model' => $model,
]);
otherwise is not showed ..
ther your code or not show the index page or not execute the sql command..
what do you are trying to do don't speed up the page load simple ( i think ) you try to do another think when the page is loading..

Related

How to use relationships in laravel 8

my question has two parts
Firstly, My if statement is not working. My if statement is as followed:
if ($request->is_published) {
$resources_page->published_at = now();
}
This is stored in my controller, I have a model for this and it is as followed:
public function is_published()
{
return $this->published_at !== null;
}
It is meant to check whether my checkbox is checked and return the timestamp, I have it cast in my model like followed:
protected $casts = [
'published_at' => 'datetime',
];
And in my view:
#include('components.form.input-checkbox', [
'label' => 'Publish?',
'form_object' => 'page',
'name' => 'is_published'
])
Could anyone elude to the answer?
Secondly, when trying to sync, it is not storing to my resources_category_resources_page table
In my controller, i have the following code
$resources_page->resources_categories()->sync(
ResourcesCategory::whereIn('slug', $request->resources_categories)->pluck('id')
);
In my model I have the relationships declared properly, so I don't know why its not storing?

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

how to use cache in cakePHP3

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'));
}

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 - How to render the index view plus the create view all in the same page

Greetings,
So far i have made Yii2 applications always rendering one view per action in some Controller.
But now i need to render 2 view files, the index and the create in the same screen.
In Yii1.xx there was renderPartial(), and in Yii2 now there is render() as substitute but don't know how to use it. I know the syntax is somewhat like app/Controller::render() but it doesn't ring a bell to me.
My actionIndex in my SiteController is:
public function actionIndex()
{
$searchModel = new TubeSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->redirect(Url::toRoute('tube/index'), [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
This action loads the tube/index page as the starting page of the app. I want to join in the load the create action. Is it possible -> two php files rendering in the same screen at statup.
many thanks...
It's not clear from your question what you are actually trying to achieve, so I'll propose two solutions, based on two very different scenarios. In both scenarios, you will need to configure the action parameter of your form to point to tube/create, otherwise your form will not submit properly.
Scenario 1 - You want the tube/index view to be rendered when your user visits site/index
Since the view tube/index seems to be about creating new models, I'll call it tube/create instead, for simplicity.
Redirecting from your index page would seem to me to be bad practice. It will double the load on your server, and the user may be confused as to why they are being redirected. The simplest way to achieve this would be just to render the tube/create view in the index action of your site controller, like this;
public function actionIndex(){
$searchModel = new TubeSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$model = new Tube();
$model->load(Yii::$app->request->post());
$model->save();
return $this->render('#app/views/tube/create', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
'model' => $model,
]);
}
Scenario 2 - You want to render two views on the same page, one to view the results of some kind of search, and the other to create a new model. This is what you might have used renderPartial() for in Yii1. you would do it like this;
I'm sticking with rendering tube/create in the site/index action.
public function actionIndex(){
$searchModel = new TubeSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$model = new Tube();
$model->load(Yii::$app->request->post());
$model->save();
return $this->render('//tube/index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
'model' => $model,
]);
}
You would then just render the two separate partial views in your tube/index view file, like this.
/** tube/index **/
echo $this->render('//tube/view', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,]);
echo $this->render('//tube/create', [
'model' => $model]
);
The two scenarios assume that your view files are located in the views/tube folder, and that you have already loaded the necessary models with a use statement at the top of the controller file.
I SOLVED IT BY MYSELF.
SITECONTROLLER CODE:
public function actionIndex()
{
return $this->redirect(Url::toRoute('tube/index'));
}
TUBECONTROLLER CODE:
public function actionIndex()
{
$searchModel = new TubeSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$model = new Tube();
if ($model->load(Yii::$app->request->post())) {
$model->save();
}
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
'model' => $model,
]);
}
This way the index and create view are together in the same page and every thing works ok.

Resources