Clean custom cache key after cache flush - caching

I am trying to cache the contents of a controller response, in the controller I setup a unique cache key and if the key does not exist it is store in cache and servers non cache result, next time the controller is called on it will return the cache value from the cache key.
But I need to clean this cacke key when the cached is cleaned from admin, I only need to clean this cache if the "block_html" has being cleaned, this is my controller code:
public function execute()
{
$resultJson = $this->resultJsonFactory->create();
try {
$cacheKey = 'unique_string';
$cachedResult = $this->_cache->load($cacheKey);
if (!$cachedResult) {
$response = [
'success' => $this->helper->isModuleEnable() ? true : false,
'message' => $this->helper->getTextValue() ? $this->helper->getTextValue() : ''
];
$cachedResult = json_encode($response);
$this->_cache->save($cachedResult, $cacheKey, [], 10);
} else {
$response = json_decode($cachedResult, true);
}
} catch (\Exception $e) {
$response = ['error' => false, 'message' => $e->getMessage()];
}
return $resultJson->setData($response);
I am not sure how to detect when the block html is being cleared, I have tried using a Observer for the event clean_cache_before or clean_cache_after but nothing, any ideas?

Related

How to flash validation errors to session in Laravel

The built in behavior for flashing back validation errors in Laravel does not seem to be working for my use case.
I have a (React) form that posts it's data via fetch API using this method, which reloads or redirects the page with (hopefully) any session data after the response is returned:
fetch(props.register_route, {
method: 'POST',
headers: {
'X-CSRF-Token': props.csrf,
},
body: data,
})
.then((result) => {
return result.json();
})
.then((result) => {
console.log(result);
window.location.href = result.url;
},
(error) => {
console.log(error);
});
In my controller, I validate this data but if I structure it as follows, the errors are not available as $errors in the resulting page
if ($validator->fails()) {
return redirect()->back()->withErrors($validator);
}
However if I manually flash the errors to the session and return a url instead of a redirect, suddenly the behavior works.
if ($validator->fails()) {
Session::flash('errors', $validator->errors());
return response->json([
'url' => route('register'),
], Response::HTTP_NOT_ACCEPTABLE);
}
I feel as if I must be doing something incorrectly here to have to use this workaround. I could also manually send the errors back in the response, which may be the right way to structure things in the long run.
when you are calling api from javascript or front end applications like Reactjs,Angular,android etc.. .So it expect return result should be in json format so it should be like
if ($validator->fails()) {
return response()->json( $validator->errors(),422);
}
if you not calling Method from direct laravel blade then pass response in JOSN Format.
like
https://laravel.com/docs/8.x/responses#json-responses
Or
make one ResponseManager File
<?PHP
namespace App\Libraries\utils;
class ResponseManager {
public static $response = array('flag' => true, 'data' => '', 'message' => '', 'code' => 01,);
public static function getError($data = '', $code = 10, $message = '', $flag = false) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}
public static function getResult($data = '', $code = 10, $message = '', $flag = true) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}}
Define in config/app.php
//custom class
'ResponseManager' => App\Libraries\utils\ResponseManager::class,
and then use in whole project
Error Message Like
if ($validation->fails()) {
$message = $validation->messages()->first();
return Response()->json(ResponseManager::getError('', 1, $message));
}
Success Message Like
return Response()->json(ResponseManager::getResult(null, 10, "Success"));

Force slack logging into queue

I have a bit of a dilemma as I need to come up with a good logger that logs what is happening in the app at the same time if there is a Log::error called, it should also notify Devs and Sys admin via slack. It is currently working, but it adds an overhead to the request-response time.
Below is my setting:
//config/logging.php
'default' => env('LOG_CHANNEL', 'stack'),
//truncated
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 0,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'App',
'emoji' => ':boom:',
'level' => 'error',
]
]
//truncated
//UserController
public function show(User $user)
{
//just a sample code, the important part is where the Log facade is called
try {
//business logic
} catch (Exception $e) {
Log::error(get_class(), [
'user_id' => $user->id,
'message' => $e->getMessage()
]);
}
return view('user.show', compact($user));
}
It is already working, but for sure we can still improve this to reduce the overhead somehow even though the added time for code above is negligible, but the real code is more complex and has quite a lot of iteration
How can I alter modify the behavior of the 'slack' logger to push it into a queue when it is triggered? I prefer to code it once and forget it rather than remembering that I have to push it to an on-demand logger such as
Log::chanel(['daily', 'slack'])->...
OR
//this is good for more on particular event notification but not not error notification which can happen anywhere
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification)`
Note:
I tried adding some code into bootstrap/app.php but it is not working
//bootstrap/app.php
$app->configureMonologUsing(function($monolog) use ($app) {
//some code here. does not work, just getting page not working
});
It is like when I call this log level and this channel, I want it to be queued
You can do like this.
1.Create Job ex: name as LogSlackQueue.php
public class LogSlackQueue implements ShouldQueue {
...
...
public function handle() {
Log::channel(['daily', 'slack'])->info($your_input);
}
}
2.Then use as
LogSlackQueue::dispatch($your_input)
If you dont want to do like above, you need to figure it out to make custom provider
Thanks to #ZeroOne for giving out idea on how to solve it. I wanted it automatic and any existing code having Log::error() will automatically prompt the devs.
Below is my solution.
//CustomSlackServiceProvider.php
try {
//listen to all events
Event::listen('*', function($event, $details) {
//check if the event message logged event which is triggered when we call Log::<level>
if($event == "Illuminate\Log\Events\MessageLogged") {
//$details contain all the information we need and it comes in array of object
foreach($details as $detail) {
//check if the log level is from error to emergency
if(in_array($detail->level, ['emergency', 'critical', 'alert', 'error'])) {
//trigger the notification
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification($detail->message, $detail->level, $detail->context));
}
}
}
});
} catch (Exception $e) {
}
//AlertDevInSlackNotification.php
class AlertDevInSlackNotification extends Notification implements ShouldQueue
{
use Queueable;
private $class;
private $level;
private $context;
public function __construct($class, $level, $context)
{
$this->class = $class;
$this->level = strtoupper($level);
$this->context = $context;
//prevent congestion in primary queue - make sure this queue exists
$this->queue = 'alert';
}
public function via($notifiable)
{
return ['slack'];
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->content($this->level.': '.$this->class)
->attachment(function($attachment) {
$attachment->fields($this->context);
});
}
UPDATE:
The code above will work when you trigger Log::error().
But to listen to an event that is being thrown by an error such as syntax error which will cause "Serialization of 'Closure' is not allowed". You can do this instead to improve coverage:
public function boot()
{
try {
//listen to all events
Event::listen('*', function($event, $details) {
//check if the event message logged event which is triggered when we call Log::<level>
if($event == "Illuminate\Log\Events\MessageLogged") {
// dump($event);
//$details contain all the information we need and it comes in array of object
foreach($details as $detail) {
$this->path = '';
$this->level = '';
$this->context = [];
$this->message = '';
//check if the log level is from error to emergency
if(in_array($detail->level, ['emergency', 'critical', 'alert', 'error'])) {
//#todo - exclude: Error while reading line from the server. [tcp://cache:6379] = restart
//check if the context has exception and is an instance of exception
//This is to prevent: "Serialization of 'Closure' is not allowed" which prevents jobs from being pushed to the queue
if(isset($detail->context['exception'])) {
if($detail->context['exception'] instanceof Exception) {
$this->level = $detail->level;
//to keep consistency on all the log message, putting the filename as the header
$this->message = $detail->context['exception']->getFile();
$this->context['user'] = auth()->check() ? auth()->user()->id.' - '. auth()->user()->first_name.' '.auth()->user()->last_name : null;
$this->context['message'] = $detail->context['exception']->getMessage();
$this->context['line'] = $detail->context['exception']->getLine();
$this->context['path'] = request()->path();
$this->runNotification();
continue;
}
}
$this->level = $detail->level;
$this->context = $detail->context;
$this->message = $detail->message;
$this->runNotification();
continue;
}
}
}
});
} catch (Exception $e) {
}
}
public function runNotification()
{
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification($this->message, $this->level, $this->context));
}

Laravel transaction saves data into DB even when it's rollbacked

I send a request, trying to save multiple documents at once.
Here is my code
DB::beginTransaction();
try {
foreach ($request->documents as $index => $documentInfo) {
// Check
if (// some statement) {
$documentExists = Document::where([
// some checks
])
->exists();
if ($documentExists) {
throw new \Exception("Error Processing Request", 1);
}
}
// Assign document properties
$document->save();
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
if ($request->ajax()) {
return response()->json([
'success' => false,
'document' => $index,
'message' => '// message',
]);
}
return redirect()->back()->with('error', '// message');
}
The thing that happens is that I check every document for its own unique values. If two documents with same values try to get saved I want to return an error.
I tried uploading two documents with the same values, the error is returned on the second document and the transaction must fail, but the first document gets saved into the database.
I don't want this, if there is an error I don't want any documents saved into the DB.

extend larasap/fb post method

I'm trying to extend the functionality of a method from this package:
https://github.com/toolkito/laravel-social-auto-posting
Because the usage from mine controller is so simple, and others package makes a big mess even for basic operation like the one I need to achieve!
Goal: -posting text over a fb page with some tags of users that have allready puted a like to the same page.
So I Start from this call:
SendTo::Facebook(
‘link’,
[
‘link’ => ‘https://github.com/toolkito/laravel-social-auto-posting',
‘message’ => ‘Laravel social auto posting’
]
);
If I simply cut the link part, the message part can be my text of the post, and all works easy.
If I try to add user's tag on the 'message' part with the notation:
#[userId]
thats not works and the tag part is cutted and only the text is showed:
If I send 'text'=>'some text #[mineuserid] more text''
only
'some text more text'
is showed on the wall.
So I move to copy and extend the methods.
If I well understand from fb documentation I can tags user with the field tags but needs to be specified even the field places (in that case if I well understand my page's id)
So I start to explore into package, and trying to mods over sends link of the package:
public static function Facebook($type, $data)
{
switch ($type) {
case 'link':
$message = isset($data['message']) ? $data['message'] : '';
$result = FacebookApi::sendLink($data['link'], $data['message']);
break;
case 'postolo':
$message = isset($data['message']) ? $data['message'] :'';
$tags =isset($data['tags']) ? $data['tags'] : '';
$places =isset($data['places']) ? $data['places'] : '';
$result = FacebookApi::sendPostolo( $message, $tags,$places);
break;
Mine part is "postolo"
Then I found sendLink:
public static function sendLink($link, $message = '')
{
self::initialize();
$data = compact('link', 'message');
try {
$response = self::$fb->post('/me/feed', $data, self::$page_access_token);
} catch(Facebook\Exceptions\FacebookResponseException $e) {
throw new \Exception('Graph returned an error: '.$e->getMessage());
} catch(Facebook\Exceptions\FacebookSDKException $e) {
throw new \Exception('Facebook SDK returned an error: '.$e->getMessage());
}
$graphNode = $response->getGraphNode();
return $graphNode['id'];
}
If I foolishly copy this and adapts it to my needs is something like:
public static function sendPostolo($link, $message = '',$tags='',$places='')
{
self::initialize();
$data = compact( 'tags','message','places');
try {
$response = self::$fb->post('/me/feed', $data, self::$page_access_token);
} catch(Facebook\Exceptions\FacebookResponseException $e) {
throw new \Exception('Graph returned an error: '.$e->getMessage());
} catch(Facebook\Exceptions\FacebookSDKException $e) {
throw new \Exception('Facebook SDK returned an error: '.$e->getMessage());
}
$graphNode = $response->getGraphNode();
return $graphNode['id'];
}
But at the end the method $fb->post() not works as espect to me, and just publishs the first data of my array $data = compact( 'tags','message','places'); so in that case 'tags' and not as tags but predictably as plain text...
this is post() on fb package:
public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null)
{
return $this->sendRequest(
'POST',
$endpoint,
$params,
$accessToken,
$eTag,
$graphVersion
);
}

How to reinitialize model when client side validation fails in Yii 2?

I am working on Yii 2 form and I want to reinitialize model when client side validation fails. For example with certain rules like below:
public function rules()
{
return [
[['username'], 'required', 'message' => 'You must enter your username'],
['username','email'],
[['password'], 'required', 'message' => 'You must enter your password'],
];
}
When validation fails I want all fields to be empty (for example when user enters invalid email address). How can I do that?
I assume you use standard Yii 2 way of loading the model:
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post()) && $model->save()) {
// ...
}
return $this->render('view', ['model' => $model]);
Set fields to null when validation fails. You don't want to create new instance (which would be easier) because you would lost all validation messages.
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post())) {
if ($model->save()) {
// ....
} else {
$model->username = null;
$model->password = null;
}
}
return $this->render('view', ['model' => $model]);
UPDATE: for the client side validation add this JS code in view:
$("#form-ID").on("afterValidateAttribute", function (event, attribute, messages) {
if (event.result === false) {
attribute.value = "";
}
});
Replace #form-ID with proper form element JS identifier.

Resources