Automate this function in any way? - php

public function onSystemCron(&$logs, &$lastRun, $force)
{
$clear_tmp = true;
$clear_logs = [];
$log_files = $this->_application->Filter('system_admin_system_logs', []);
foreach ($log_files as $log_name => $log) {
$clear_logs[$log_name] = empty($log['days']) ? 365 : $log['days'];
}
if (!isset($lastRun[$this->_name])) {
$lastRun[$this->_name] = [];
} elseif (!is_array($lastRun[$this->_name])) {
$lastRun[$this->_name] = [
'tmp' => $lastRun[$this->_name]
];
}
if (!$force) {
if (!empty($lastRun[$this->_name]['tmp'])
&& time() - $lastRun[$this->_name]['tmp'] < 604800
) {
// Less than a week since last run so do not clear tmp
$clear_tmp = false;
}
<?php
namespace -\System\Tool;
class RunCronTool extends AbstractTool
{
protected function _systemToolInfo()
{
return [
'label' => __('Run cron', 'directories'),
'description' => __('Use this tool to manually run cron.', 'directories'),
'weight' => 15,
];
}
public function systemToolRunTask($task, array $settings, $iteration, $total, array &$storage, array &$logs)
{
$this->_application->callHelper('System_Cron', [&$logs, true]);
return 1;
}
}
I have an issue with a WordPress plugin. It offers a button which will force run cron. Usually, making use of this button is not necessary. I tested my WP Cron and it's working fine, so the plugin is the problem. Any data which is in relation to the plugin does not get updated by WP cron. So I'm forced to click this button manually.
So, is there any solution to automate the function of the button?
I mean, I have to open the plugin settings and click on a button to run Cron. Is there any chance to automate this process?
Can provide more information if needed.
Many thanks.
Yes, I have no idea what I'm doing thanks.

When the OS wants to "kick" WordPress's cron system, it does an HTTP(S) GET to example.com/wp-cron.php .
It's possible to do this from php code, like so.
if ( ! wp_doing_cron() ) {
$url = get_site_url( null, 'wp-cron.php' );
$req = new \WP_Http();
$req->get( $url );
}
Do this last in a page view. I do it from the shutdown hook. It won't hang your page because wp-cron.php uses fastcgi_finish_request() to respond immediately to its client.
This lets you run cron from your code if DISABLE_WP_CRON is true or in other circumstances where it won't get called on its own.
But be careful. Don't use this unless you really need it. It's a bad idea to work around a site owner's need to set DISABLE_WP_CRON.

Related

Laravel 9 multiple tag flush not working when flushing individual tags

I've noticed an issue with my Laravel application, and i'm not quite sure if i'm just doing something stupidly wrong, or there is a genuine issue.
So i'm storing and fetching my cached data with multiple tags on my App\Models\User\User model like below
public function getSetting(String|Bool $key = false) : Mixed {
//Try
try {
return cache()->tags(["user_{$this->id}_cache", 'user_settings'])->remember("user_{$this->id}_settings", now()->addDays(1), function(){
return $this->settings()->pluck('value', 'key')->toArray();
});
} catch(\Exception $e){
//Logging errors here
}
}
This function simply grabs all of the users settings and returns an array.
I am using 2 cache tags because I want to cover both scenarios
The ability to be able to remove all cached items for a specific model (User)
The ability to be able to remove a specific type of cache across all models (Users)
The Laravel cache documentation simply states to pass the tag (or tags as an array) that you want to remove.
So my thinking is that if I want to clear user settings cache for all users, I should be able to run the following
cache()->tags('user_settings')->flush();
and if I want to remove all cache for a specific user, I should be able to run
cache()->tags('user_1_cache')->flush();
But for some reason, only the second example (using user_1_cache) works? If I run the first example and try to clear all cache with the tags user_settings, the function returns true but does not clear the cache?
Am I doing something stupidly wrong or just completely misunderstanding how the cache tags work?
Versions
PHP - 8.1
Laravel - 9.3.8
Cache driver - Redis
I reproduced your scenario here. It's working as stated in the docs.
class User extends Model
{
protected $fillable = ['id', 'name'];
public function cacheSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->remember("{$this->id}_settings", now()->addDay(), function () {
return $this->only('name');
});
}
public function getSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->get("{$this->id}_settings");
}
public function getUserCacheKey()
{
return "user_{$this->id}_cache";
}
}
These tests run with no problem:
public function test_cache_flush_all_users()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags('user_settings')->flush();
// both deleted
$this->assertNull($alice->getSettings());
$this->assertNull($john->getSettings());
}
public function test_cache_flush_specific_user()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags($alice->getUserCacheKey())->flush();
// only alice deleted
$this->assertNull($alice->getSettings());
$this->assertNotNull($john->getSettings());
}
Not having all details of your implementation, perhaps you can figure out what is causing the issue.

Laravel CRON or Event process respond to api request via long poll - how to re-vitalise the session

I have a poll route on an API on Laravel 5.7 server, where the api user can request any information since the last poll.
The easy part is to respond immediately to a valid request if there is new information return $this->prepareResult($newData);
If there is no new data I am storing a poll request in the database, and a cron utility can then check once a minute for all poll requests and respond to any polls where data has been updated. Alternatively I can create an event listener for data updates and fire off a response to the poll when the data is updated.
I'm stuck with how to restore each session to match the device waiting for the update. I can store or pass the session ID but how do I make sure the CRON task / event processor can respond to the correct IP address just as if it was to the original request. Can php even do this?
I am trying to avoid websockets as will have lots of devices but with limited updates / interactions.
Clients poll for updates, APIs do not push updates.
REST API's are supposed to be stateless, so trying to have the backend keep track goes against REST.
To answer your question specifically, if you do not want to use websockets, the client app is going to have to continue to poll the endpoint till data is available.
Long poll is a valid technique. i think is a bad idea to run poll with session. since session are only for original user. you can run your long poll with php cli. you can check on your middleware to allow cli only for route poll. you can use pthreads
to run your long poll use pthreads via cli. and now pthreads v3 is designed safely and sensibly anywhere but CLI. you can use your cron to trigger your thread every one hour. then in your controller you need to store a $time = time(); to mark your start time of execution. then create dowhile loop to loop your poll process. while condition can be ($time > time()+3600) or other condition. inside loop you need to check is poll exist? if true then run it. then on the bottom of line inside loop you need to sleep for some second, for example 2 second.
on your background.php(this file is execute by cron)
<?php
error_reporting(-1);
ini_set('display_errors', 1);
class Atomic extends Threaded {
public function __construct($data = NULL) {
$this->data = $data;
}
private $data;
private $method;
private $class;
private $config;
}
class Task extends Thread {
public function __construct(Atomic $atomic) {
$this->atomic = $atomic;
}
public function run() {
$this->atomic->synchronized(function($atomic)
{
chdir($atomic->config['root']);
$exec_statement = array(
"php7.2.7",
$atomic->config['index'],
$atomic->class,
$atomic->method
);
echo "Running Command".PHP_EOL. implode(" ", $exec_statement)." at: ".date("Y-m-d H:i:s").PHP_EOL;
$data = shell_exec(implode(" ", $exec_statement));
echo $data.PHP_EOL;
}, $this->atomic);
}
private $atomic;
}
$config = array(
"root" => "/var/www/api.example.com/api/v1.1",
"index" => "index.php",
"interval_execution_time" => 200
);
chdir($config['root']);
$threads = array();
$list_threads = array(
array(
"class" => "Background_workers",
"method" => "send_email",
"total_thread" => 2
),
array(
"class" => "Background_workers",
"method" => "updating_data_user",
"total_thread" => 2
),
array(
"class" => "Background_workers",
"method" => "sending_fcm_broadcast",
"total_thread" => 2
)
);
for ($i=0; $i < count($list_threads); $i++)
{
$total_thread = $list_threads[$i]['total_thread'];
for ($j=0; $j < $total_thread; $j++)
{
$atomic = new Atomic();
$atomic->class = $list_threads[$i]['class'];
$atomic->method = $list_threads[$i]['method'];
$atomic->thread_number = $j;
$atomic->config = $config;
$threads[] = new Task($atomic);
}
}
foreach ($threads as $thread) {
$thread->start();
usleep(200);
}
foreach ($threads as $thread)
$thread->join();
?>
and this on your controller
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Background_workers extends MX_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
$this->output->enable_profiler(FALSE);
$this->configuration = $this->config->item("configuration_background_worker_module");
}
public function sending_fcm_broadcast() {
$time_run = time();
$time_stop = strtotime("+1 hour");
do{
$time_run = time();
modules::run("Background_worker_module/sending_fcm_broadcast", $this->configuration["fcm_broadcast"]["limit"]);
sleep(2);
}
while ($time_run < $time_stop);
}
}
this is a sample runing code from codeigniter controller.
Long polling requires holding the connection open. That can only happen through an infinite loop of checking to see if the data exists and then adding a sleep.
There is no need to revitalize the session as the response is fired only on a successful data hit.
Note that this method is very CPU and memory intensive as the connection and FPM worker will remain open until a successful data hit. Web sockets is a much better solution regardless of the number of devices and frequency of updates.
You can use notifications. "browser notification" for web clients and FCM and APN notification for mobile clients.
Another option is using SSE (server sent events). It's a connection like socket but over http. Client sends a normal request, and server can just respond to client multiple times and any time if client is available (In the same request that has been sent).

Find Method Empty Array

I have an application with a third party plugin that handles ACL management, works fine, but I am having some issues with a isAuthorized function. The user is being passed into the function and when I debug the variable I can see all of the correct information in the dataset, but when the script executes the find query it returns empty. Here is the thing, the model I am executing the query on is apart of the plugin I am using. So I am executing a find within my application on a model that lies within my plugin. To me it isn't ideal, nonetheless I am working with a plugin that wasn't built application specific. In order to execute the find the Author added
App::uses('User', 'AuthAcl.Model');
so the find could be possible. Now that said, I tweaked one thing in the conditions part of the statement because I was getting Column 'id' in field list is ambiguous error from SQL. I started to get that error because I added some relationships to the application.
I say all that to say this - since I am loading the model via App Uses shouldn't I be able to execute a find like so
$this->User->Find('First');
I have tried that and it says I'm executing on a non object. Here is the code for that script. I need input. When I execute my script without debugging it locks me out the application, and by reading the code and debugging it I think this happens because the find is returning empty. And on another note - please bear with me - I didn't write this code, and I am in the phase of learning to understand other developers code. I am using CAKEPHP 2.3 Thanks guys!
public function isAuthorized($user = null) {
App::uses('User', 'AuthAcl.Model');
App::uses('Group', 'AuthAcl.Model');
$authFlag = false;
$this->set('login_user',$user);
$userModel = new User();
$group = new Group();
//die(debug($user));
$rs = $this->$userModel->find('first',array('conditions'=>array('user.id' => $user['id'])));
die(debug($rs));
$action = 'controllers';
if (!empty($this->plugin)){
$action .= '/'.$this->plugin;
}
$action .= '/'.$this->name;
$action .= '/'.$this->action;
if (!empty($rs['Group'])){
foreach ($rs['Group'] as $group){
$authFlag = $this->Acl->check(array('Group' => array('id' => $group['id'])), $action);
if ($authFlag == true){
break;
}
}
}
if ($authFlag == false && !empty($user)){
$authFlag = $this->Acl->check(array('User' => array('id' => $user['id'])), $action);
//die(debug($authFlag));
}
if ($authFlag == false && !empty($user)){
$this->redirect(array('controller' => 'accessDenied', 'action' => 'index','plugin' =>'auth_acl'));
}
if (!empty($user)){
$user = $userModel->find('first',array('conditions' => array('user.id' => $user['id'])));
$this->Session->write('auth_user',$user);
$this->request->data['auth_plugin'] = $this->plugin;
$this->request->data['auth_controller'] = $this->name;
$this->request->data['auth_action'] = $this->action;
}
return $authFlag;
}
}

PHP APC problems when storing objects

I am trying to build an configuration parser for my application I installed APC today, but everytime I try to put an serialized object in the store, it does not get in there and does not. (I am checking with apc.php for my version[3.1.8-dev] on PHP 5.3.16 [My Dev Environment], so I am sure that the data is not in the cache). this is how I pass the data to the cacher:
// The data before the caching
array (
'key' => md5($this->filename),
'value' => serialize($this->cfg)
);
// The caching interface
function($argc){
$key = $argc['key'];
Cache\APC::getInstance()->set($key,$argc['value']);
}
// The caching method described above
public function set($key, $val) {
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
else
return false;
}
// the constructor of the configuration class.
// It 1st looks for the configuration in
// the cache if it is not present performs the reading from the file.
public function __construct($filename = '/application/config/application.ini',
$type = self::CONFIG_INI)
{
if (defined('SYSTEM_CACHE') && SYSTEM_CACHE === 'APC'){
$key = md5($filename);
$cfg = APC::getInstance()->get($key);
if (!empty($cfg)) {
print "From Cache";
$this->cfg = unserialize($cfg);
return;
} else {
print "From File";
}
}
}
I did a few tests and there is not a problem with the MD5() key (which I thought while writing this question) nor with APC itself. I am really stuck on this one, nothing odd in the logs, so if anyone can give me at least some directions will be very appreciated.
Thanks in advance!
The problem is was in my code:\
public function set($key, $val) {
/*
*
* If the key exists in the cache delete it and store it again,
* but how could it exist when the else clause is like that...
*/
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
// This is very wrong in the current case
// cuz the function will never store and the if will
// never match..
else
return false;
}
NOTE:
Always think and keep your eyes open, if you still can't find anything get off the PC and give yourself a rest. Get back after 10-15 minutes and pown the code. It helps! :D

PHP Asynchronous Method Call In The Yii Framework

Question
I want to know if it is possible to asynchronously invoke a Yii controller method from one of its actions while the action renders a view, leaving the method to complete a long running operation. I would love to do something like the code below and I don't need to return a result from my_long_running_func.
public function actionCreate() {
$model = new Vacancies;
if (isset($_POST['Vacancies'])) {
$model->setAttributes($_POST['Vacancies']);
$model->save();
//I wish :)
call_user_func_async('my_long_running_func',$model);
}
$this->render('create', array( 'model' => $model));
}
Problem
I am trying to write a controller action in Yii that posts a vacancy and notifies interested subscribers of the post. The problem is that it takes a long time to execute the notification query.
Now I am searching for a way to asynchronously run the query so the poster sees his response in as little time as possible while the query runs in the background in a way similar to C# delegates or events.
The solutions I googled up performed asynchronous request(s) during the course of the controller action but all I want to do is to run a method of the controller asynchronously and the action had to wait till the request(s) were completed.
Attempted
I have tried the following methods but the query is still slow for my test data of about 1500 users.
Yii ActiveRecord
if ($vacancy->save()) {
if($vacancy->is_active == 1) {
$url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));
$trainees = YumUser::getUsersByRole('Trainees');
if($trainees!=null) {
foreach($trainees as $trainee){
$message = new YumMessage;
$message->from_user_id = Yii::app()->user->id;
$message->title = 'Vacancy Notification: '.date('M j, Y');
$message->message = "A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
$message->to_user_id = $trainee->id;
$message->save();
}
}
}
}
Yii Data Access Objects
if ($vacancy->save()) {
if($vacancy->is_active == 1) {
$url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));
$trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
$fid=Yii::app()->user->id;
$msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
$ts = time();
$tt = 'Vacancy Notification: '.date('M j, Y');
if($trainee_ids!=null) {
foreach($trainee_ids as $trainee_id){
Yii::app()->db->createCommand()
->insert('message',array('timestamp'=>$ts,'from_user_id'=>$fid,'to_user_id'=>$tid,'title'=>$tt,'message'=>$msg));
}
}
}
}
Prepared Statements
if ($vacancy->save()) {
if($vacancy->is_active == 1) {
$url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));
$trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
$fu=Yii::app()->user->id;
$msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
$ts = time();
$tt = 'Vacancy Notification: '.date('M j, Y');
$sql="INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) VALUES (:ts,:fu,:tt,:msg,:tu)";
if($trainee_ids!=null) {
foreach($trainee_ids as $trainee_id){
$command=Yii::app()->db->createCommand($sql);
$command->bindParam(":ts",$ts,PDO::PARAM_INT);
$command->bindParam(":fu",$fu,PDO::PARAM_INT);
$command->bindParam(":tt",$tt,PDO::PARAM_STR);
$command->bindParam(":msg",$msg,PDO::PARAM_STR);
$command->bindParam(":tu",$trainee_id,PDO::PARAM_INT);
$command->execute();
}
}
}
}
Research
I have also checked the following websites (I'm only allowed to post two links) but they either require the action to wait for the request to be completed or need curl (which I don't have access to on the deployment server) or need an external library. I was hoping for a native PHP implementation.
PHP Simulated Multi-Threading
Multithreading in php
Asynchronous PHP calls?
Asynchronous processing in PHP
Edit
I was able to decrease response time considerably by rewriting my query in this way (moving the user loop to the database layer):
public function actionCreate() {
$user=YumUser::model()->findByPk(Yii::app()->user->id);
$model = new Vacancies;
$model->corporate_id=$user->professional->institution->corporate->id;
$model->date_posted=date('Y-m-d');
$model->last_modified=date('Y-m-d H:i:s');
if (isset($_POST['Vacancies'])) {
$model->setAttributes($_POST['Vacancies']);
if ($model->save()) {
if($model->is_active == 1) {
$url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));
$fu=Yii::app()->user->id;
$msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
$ts = time();
$tt = 'New Vacancy: '.$model->title;
$sql='INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) SELECT :ts,:fu,:tt,:msg,t.user_id FROM trainee t';
Yii::app()->db->createCommand($sql)->execute(array(':ts'=>$ts,':fu'=>$fu,':tt'=>$tt,':msg'=>$msg));
}
if (Yii::app()->getRequest()->getIsAjaxRequest())
Yii::app()->end();
else
$this->redirect(array('view', 'id' => $model->id));
}
}
$this->render('create', array( 'model' => $model));
}
Notwithstanding, it would be nice if someone could post a way to call functions asynchronously.
Typically, the solution for these kind of problems would be to integrate a message-bus in your system. You could consider a product like Beanstalkd. This requires installing software on your server.
I suppose this suggestion would be called "using an external library".
If you can access the deployment server and you can add cronjob (or maybe a sysadmin can) you could consider a cronjob that does a php-cli call to a script that reads jobs from a job queue in your database which is filled by the controller method.
If you cannot install software on the server you're running, you could consider using a SAAS solution like Iron.io to host the bus functionality for you. Iron.io is using what is called a push queue. With a push queue the message bus actively performs a request (push) to the registered listeners with the message content. This might work since it doesn't require you to do a curl request.
If none of the above is possible, your hands are tied. Another post which is quite relevant on the subject: Scalable, Delayed PHP Processing
I would try this, though I'm not 100% that Yii will work properly, but its relatively simple and worth a go:
public function actionCreate() {
$model = new Vacancies;
if (isset($_POST['Vacancies'])) {
$model->setAttributes($_POST['Vacancies']);
$model->save();
//I wish :)
}
HttpResponse::setContentType('text/html');
HttpResponse::setData($this->render('create', array( 'model' => $model), true);
HttpResponse::send();
flush(); // writes the response out to the client
if (isset($_POST['Vacancies'])) {
call_user_func_async('my_long_running_func',$model);
}
}
Here's an entirely different type of suggestion. What about registering for the onEndRequest event that is fired by CWebApplication's end() function?
public function end($status=0, $exit=true)
{
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
if($exit)
exit($status);
}
You'd need to register for the event and figure out how to pass your model in somehow, but the code would properly run after all the data has been flushed to the browser ...

Categories