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'),
],
],
];
Related
Really sorry if I've missed something here, but I have searched the issues and docs high and low, and I cannot find why this is not working. Perhaps I need another tutorial on using the internet ;)
Consider the following entity annotation, in the openapi_context section the body.description and responses.200.description have no effect at all, and its driving me slightly mad... You should know that I am using de/normalization contexts but that doesn't seem related so I have left it out.
/**
* User entity
*
* #API\ApiResource(
* collectionOperations={
* "authenticate"={
* "method"="POST",
* "status"=200,
* "path"="/authenticate",
* "controller"=UserLoginController::class,
* "openapi_context"={
* "responses"={"200"={"description"="API token and secret"}},
* "body"={"description"="Login details"},
* "summary"="User authentication",
* "description"="Provides auth tokens on success",
* },
* },
* }
* )
*
*/
class User implements UserInterface
{
...
}
The result (blue blocks as expected, red blocks not working):
I have spent way too much time on this issue as it is, I would really appreciate it if someone could help me put this to bed. I have checked/tried the following to no avail;
Creating Custom Operations and Controllers
Custom Symfony Action with API Platform bundle
[Question] Documentation API with Swagger #143
Changing Operations in the OpenAPI Documentation
Composer versions (relevant parts):
{
"require": {
"php": ">=7.2.5",
"api-platform/core": "^2.6",
"symfony/framework-bundle": "5.2.*"
}
}
After some experimenting, I found that the openapi_context annotation attribute does indeed seems to ignore the response documentation. It does however allow you to provide the request body description that you are missing:
#[ApiResource(
collectionOperations: [
'test' => [
'method' => 'POST',
'path' => '/test',
'openapi_context' => [
'summary' => 'The endpoint summary',
'description' => 'The endpoint description',
'requestBody' => [
'description' => 'The endpoint request body description', // This one
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-some.group'
],
],
],
],
],
]
],
)]
I'm using PHP 8.0.3 and API Platform 2.6.3 while writing this, changing the annotations to docblocks should result in the same outcome for you though.
In order to document the endpoint response specification however, I had to implement a custom OpenApiFactoryInterface:
<?php declare(strict_types = 1);
namespace App;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\Model\Operation;
use ApiPlatform\Core\OpenApi\Model\PathItem;
use ApiPlatform\Core\OpenApi\Model\RequestBody;
use ApiPlatform\Core\OpenApi\OpenApi;
use ArrayObject;
use UnexpectedValueException;
class MyResourceOpenApiFactory implements OpenApiFactoryInterface
{
private OpenApiFactoryInterface $openApiFactory;
public function __construct(OpenApiFactoryInterface $openApiFactory)
{
$this->openApiFactory = $openApiFactory;
}
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->openApiFactory)($context);
$components = $openApi->getComponents();
$schemas = $components->getSchemas();
if (null === $schemas) {
throw new UnexpectedValueException('Failed to obtain OpenApi schemas');
}
$pathItem = new PathItem(
'MyResource test endpoint',
'A test summary',
'A test description',
null,
null,
// Your custom post operation
new Operation(
'testMyResourceCollection', // the operation route name
[
'MyResource' // your resource name
],
[
// response specifications
'201' => [
'description' => 'test endpoint 201 response description',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-read', // your resource (read) schema
],
],
],
],
],
'A test endpoint summary',
'A test endpoint description',
null,
[],
new RequestBody(
'A test request body description',
new ArrayObject([
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/MyResource-write', // your resource (write) schema
],
],
]),
),
),
);
$paths = $openApi->getPaths();
$paths->addPath('/my_resources/test', $pathItem);
return $openApi;
}
}
Have this OpenApiFactoryInterface implementation wired by the service container as a decorator to api_platform.openapi.factory:
App\MyResourceOpenApiFactory:
decorates: 'api_platform.openapi.factory'
autoconfigure: false
Change the references to the example MyResource name to a resource name of your choice (like User).
Sidenote:
This whole process of customizing OpenApi endpoint documentation in API Platform is currently quite convoluted in my opinion. Use the implementation I provided as a reference to your own implementation, as you most likely need to make a few adjustments to it in order to make it satisfy your specific use case.
I am using laravel-backpack 4.0. want to add a password change link with a page and all the functionality with validation and all, to a listing of users like edit, in the Action column.
It seems a bit odd to want another page where you can update only the password when the package's built in user crud lets you update that, and all the other user fields. That said, assuming you have your reasons (and that I've understood the usage correctly), one approach would be to use the users addon package suggested but then make a second CRUD controller for the user model that only supports the "update" operation and only allows editing the password.
NOTE
This is untested so there might be some minor issues to iron out, but the approach is sound.
Install and configure the users addon package. Then, create a second controller for users but edit such that only the "update" action is allowed and only the password and password confirmation fields are editable. We'll make the name and email read only so you can see who its for but cant edit those fields. You can make those fields hidden if you want, or remove them, but if you remove them, note that you'll need to create a custom request class and update the rules to not require those fields on submission.
<?php
namespace App\Http\Controllers;
use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\CRUD\app\Http\Requests\CrudRequest;
use EduardoArandaH\UserManager\app\Http\Requests\UserStoreCrudRequest as StoreRequest;
use EduardoArandaH\UserManager\app\Http\Requests\UserUpdateCrudRequest as UpdateRequest;
class UserPasswordCrudController extends CrudController
{
use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation { update as traitUpdate; }
public function setup()
{
$this->crud->setModel(config('backpack.permissionmanager.models.user'));
$this->crud->setEntityNameStrings('User Password', 'User Passwords');
$this->crud->setRoute(backpack_url('userPasswords'));
$this->crud->denyAccess('create');
$this->crud->denyAccess('list');
$this->crud->denyAccess('delete');
$this->crud->denyAccess('reorder');
$this->crud->denyAccess('revisions');
}
public function setupUpdateOperation()
{
$this->addUserFields();
$this->crud->setValidation(UpdateRequest::class);
}
/**
* Update the specified resource in the database.
*
* #return \Illuminate\Http\RedirectResponse
*/
public function update()
{
$this->crud->setRequest($this->crud->validateRequest());
$this->crud->setRequest($this->handlePasswordInput($this->crud->getRequest()));
$this->crud->unsetValidation(); // validation has already been run
return $this->traitUpdate();
}
/**
* Handle password input fields.
*/
protected function handlePasswordInput($request)
{
// Remove fields not present on the user.
$request->request->remove('password_confirmation');
$request->request->remove('roles_show');
$request->request->remove('permissions_show');
// Encrypt password if specified.
if ($request->input('password')) {
$request->request->set('password', Hash::make($request->input('password')));
} else {
$request->request->remove('password');
}
return $request;
}
protected function addUserFields()
{
$this->crud->addFields([
[
'name' => 'name',
'label' => trans('backpack::permissionmanager.name'),
'type' => 'text',
'attributes' => ['readonly' => 'readonly'],
],
[
'name' => 'email',
'label' => trans('backpack::permissionmanager.email'),
'type' => 'email',
'attributes' => ['readonly' => 'readonly'],
],
[
'name' => 'password',
'label' => trans('backpack::permissionmanager.password'),
'type' => 'password',
],
[
'name' => 'password_confirmation',
'label' => trans('backpack::permissionmanager.password_confirmation'),
'type' => 'password',
],
]);
}
}
Load the route for the new controller:
<?php
Route::group([
'namespace' => 'App\Http\Controllers',
'prefix' => config('backpack.base.route_prefix', 'admin'),
'middleware' => ['web', backpack_middleware()],
], function () {
Route::crud('userPasswords', 'UserPasswordCrudController');
});
Create a custom button at resources/views/vendor/backpack/crud/buttons/update_password.blade.php with this content:
#if ($crud->hasAccess('update'))
<!-- Single edit button -->
<i class="la la-edit"></i>Edit Password
#endif
Finally, in your normal user crud controller (or whatever controller you want the button in) add the button to the line stack in your controller's setupListOperation method:
public function setupListOperation()
{
$this->crud->addButtonFromView('line', 'update_password', 'view', 'end');
// ... normal setup code
}
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'));
}
I try to migrate from ZF2 to ZF3 but many viewHelpers and validators do not work. But only the ones who overwrite ZendFrameworks viewhelpers / validators do not work...
I want f.e. to overwrite the CSRF validator to allow a higher timeout by default.
I have the following application configuration:
$config = array(
// This should be an array of module namespaces used in the application.
'modules' => array(
'Zend\Cache',
'Zend\Db',
'Zend\Log',
'Zend\Mail',
'Zend\Mvc\Console',
'Zend\Mvc\I18n',
'Zend\I18n',
'Zend\Mvc\Plugin\FilePrg',
'Zend\Form',
'Zend\Hydrator',
'Zend\InputFilter',
'zend\Form',
'Zend\Filter',
'Zend\Mvc\Plugin\FlashMessenger',
'Zend\Mvc\Plugin\Identity',
'Zend\Mvc\Plugin\Prg',
'Zend\Navigation',
'Zend\Paginator',
'Zend\Serializer',
'Zend\ServiceManager\Di',
'Zend\Session',
'Zend\Router',
'Zend\Validator',
'DoctrineModule',
'DoctrineORMModule',
'TwbBundle',
'AssetManager',
#'Reliv\ElFinder',
'ZfcUser', //https://github.com/ZF-Commons/ZfcUser
'ZfcUserDoctrineORM',
'BjyAuthorize', // https://github.com/bjyoungblood/BjyAuthorize
'Base',
'Product',
'Blog',
'Admin'
),
// These are various options for the listeners attached to the ModuleManager
'module_listener_options' => array(
// This should be an array of paths in which modules reside.
// If a string key is provided, the listener will consider that a module
// namespace, the value of that key the specific path to that module's
// Module class.
'module_paths' => array(
'./module',
'./vendor',
),
// An array of paths from which to glob configuration files after
// modules are loaded. These effectively override configuration
// provided by modules themselves. Paths may use GLOB_BRACE notation.
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
// Whether or not to enable a configuration cache.
// If enabled, the merged configuration will be cached and used in
// subsequent requests.
// 'config_cache_enabled' => true,
// The key used to create the configuration cache file name.
//'config_cache_key' => $stringKey,
// Whether or not to enable a module class map cache.
// If enabled, creates a module class map cache which will be used
// by in future requests, to reduce the autoloading process.
// 'module_map_cache_enabled' => true,
// The key used to create the class map cache file name.
#'module_map_cache_key' => $stringKey,
// The path in which to cache merged configuration.
'cache_dir' => "data/cache/",
// Whether or not to enable modules dependency checking.
// Enabled by default, prevents usage of modules that depend on other modules
// that weren't loaded.
// 'check_dependencies' => true,
),
// Used to create an own service manager. May contain one or more child arrays.
//'service_listener_options' => array(
// array(
// 'service_manager' => $stringServiceManagerName,
// 'config_key' => $stringConfigKey,
// 'interface' => $stringOptionalInterface,
// 'method' => $stringRequiredMethodName,
// ),
// )
// Initial configuration with which to seed the ServiceManager.
// Should be compatible with Zend\ServiceManager\Config.
// 'service_manager' => array(),
);
Module config of Base module:
namespace Base;
...
return [
...
'validators' => array(
'invokables' => [
\Zend\Validator\Csrf::class => Validator\Csrf::class
]
)
...
]
Base\Validator\Csrf:
<?php
namespace Base\Validator;
class Csrf extends \Zend\Validator\Csrf
{
protected $timeout = 1;
public function __construct($options = [])
{
parent::__construct($options);
die("THIS DOES NOT GETTING PRINTED! NOR DOES THE BREAKPOINT HIT.");
}
}
EDIT: Added autoload config
composer.json:
"autoload": {
"psr-4": {
"Base\\": "module/Base/src/"
}
}
EDIT 2: Possible bug in implementation of \Zend\Form\Element\Csrf ?
Interesting, the CsrfValidator just gets directly instantiated here...
/**
* Get CSRF validator
*
* #return CsrfValidator
*/
public function getCsrfValidator()
{
if (null === $this->csrfValidator) {
$csrfOptions = $this->getCsrfValidatorOptions();
$csrfOptions = array_merge($csrfOptions, ['name' => $this->getName()]);
$this->setCsrfValidator(new CsrfValidator($csrfOptions));
}
return $this->csrfValidator;
}
Stacktrace (breakpoint in \Zend\Validator\Csrf __construct())
StaticPage is another module of mine.
I also debugged with xdebug and set a break point in the CsrfFactory (return statement) to see, if it is used (but it isn't).
I thought I can overwrite services / validators etc. easily in ZF3... Did I miss something?
You can use a delegator to change the validator attached to the form element when the element is instantiated. Essentially a delegator allows you (in this case) to modify the form element after it has been constructed - the idea is explained well here.
In your case you would create a class:
<?php
namespace Base\Delegator;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\Validator\Csrf as CsrfValidator;
class CsrfDelegatorFactory implements DelegatorFactoryInterface
{
public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
{
// construct the Csrf form element
$element = call_user_func($callback);
// set the validator with chosen timeout and other options
$element->setCsrfValidator(new CsrfValidator(
[
// ...
'timeout' => 10000
]
));
return $element;
}
}
Then map the delegator to Zend\Form\Element\Csrf in your application module.config.php:
'form_elements' => [
// ...
'delegators' => [
\Zend\Form\Element\Csrf::class => [
0 => \Base\Delegator\CsrfDelegatorFactory::class
],
]
],
Note that this is only changing the validator assigned by default to a Zend\Form\Element\Csrf and a csrf validator obtained by other means will not be affected.
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..