Cakephp cron job to call a controller's action - php

I started to use CakePHP (1.2) a few months ago to add small features to the company's application and I'm not too familiar with it.
We test locally then on a development server before merging to a production server.
I want a controller action to be called every hour with what I assumed to be the best way to do this through my researches, a cron job.
Attempt 1
After reading these,
http://bakery.cakephp.org/articles/mathew_attlee/2006/12/05/calling-controller-actions-from-cron-and-the-command-line
http://book.cakephp.org/1.2/en/view/110/Creating-Shells-Tasks
I could implement something without errors, but the action is not executed.
Based on these examples, I added a file named cron_dispatcher.php in my app directory (not app/webroot) and then did this command from the app dir
php cron_dispatcher.php /controller/action/param
Still nothing happened but it works perfect when I call it through the url.
Attempt 2
I tried creating a shell (email.php) that would call the action in /app/vendors/shells/.
<?php
class EmailShell extends Shell {
public function main() {
$this->out('Test');
}
}
?>
This successfully outputs Test in the console using
cake email main
but then I cannot find how to call the controller's action. I have tried
$this->requestAction('/controller/action');
I have also tried to make the call from a different function than the main in the shell.
I have tried to include the controller in the $uses variable as I would with a model but that didn't work (and it doesn't make sense I think)
I don't think creating a task is the solution either as I don't want to duplicate the sendEmails function hence why I'm looking for a way to just call the controller's action from a shell or whatever!
There is probably some theory I'm missing, thanks
Solution
I moved some methods from the controller to a model and I was able to call them from a shell.
App::import('Component', 'Email');
class SendMemosShell extends Shell {
var $uses = array(
'Memo',
);
public function main() {
}
public function sendEmails () {
$this->Email =& new EmailComponent(null);
$memoList = $this->Memo->getMemos();
//...
}
}
This link helped
http://book.cakephp.org/2.0/en/console-and-shells/cron-jobs.html
edit : clarified some of the information and added the solution

It is a quite common issue actually, ran into it also.
A controller is deciding how to handle a request and starting that task. In this case there is no need for a controller since you have a shell task, the task is already clear.
Knowing that, it does not make sense to call a controller method.
So rethink your options, and yes this is a quite difficult one. For example you might decide that sending the e-mail is a business logic step so it should be in the model. Another option is to separate it totally (that's what we like most).
In that case you will have to create a queue where you put in all e-mails to send. That is a good design since you then know the amount of logic in the controller goes down and it is separated. That way you get an e-mail service.
For example you could ask the service to send a "new user" mail. Then you add the User object to it and it should handle itself. That way you can even scale since your service could be for example outsourced, you could expand multiple servers on the service etc.
Edit:
Good questions.
Steps to take:
Centralize the "sending e-mail" process first. So choose one location where to put it. The you can decide: Add to send e-mail to a queue or call the service directly. For example you could add shell task for sending the e-mails.
Call the shell: Now you have the problem to call the shell. In general you don't want to. Why not? Because a shell (a task) could run for a long time. So that's why we use queues in between. So you can ask the queue or let the queue message you that something is done. For example think about a mail server which is down. You have to retry etc. That should not be in a web request because the user is waiting for response.
Third step is to call the shell from your cron, now that's easy since you are already on the command line so you could use standard calls.
Anyhow, there are options to do a direct call from a controller but you should not. This post gives some very interesting insights:
CakePHP: Run shell job from controller
Edit 31/08/'13: See the events system of CakePHP also for some examples: http://book.cakephp.org/2.0/en/core-libraries/events.html

Depending on what needs to be done, I often keep these methods in my controller actions. At the top of the action I check $_SERVER['REMOTE_ADDR'] == $_SERVER['SERVER_ADDR'] to ensure only the website can call the action. Then in cron I would curl or wget this address.
It has its benefits - easier to run locally during development (just enter the url in your browser), plus there are some differences between running cli version of php and apache version, as well as the request variables (eg. cake can't get the website domain/address through cli like you can running as apache module, so absolute links to the website using html helper don't work).

Related

Output progress for console and web applications

I'm writing an yii2 app that is mainly used as an console application. I have components or (micro)services that fetches some data from a server, handle them and save the information I need to db. Of course there are several for loops and in these loops I output my current progress with the use of yii\helpers\Console::updateProgress or just echo to watch the progress and testing output (like starting with xxx products). On some events I log important informations with Yii::info() or Yii::error() and so on. Normally a cron handling tasks like pullProductUpdates or something else and i.
However in some cases I need the method (i.e. pullProductUpdates) in my webapplication too. But then there must not be any echo command active or Console::updateProgress commands.
Of course I don't have problems with the logging methods from Yii because I configured the log targets and they will not echoing something. But I'm uncertain how to handle the echo commands...
One way is to check wether $_SERER['REMOTE_ADDR'] is set or not. In console it will evaluate to null so I can warp an if {} else {} around. A probably better solution is to write a log() or progress() method. A trait could be useful?
So how should I design a solution? Is there any pattern for this? Should my services implement an interface like loggable or proressable? Or use an Logger/ Progress objects and use some kind of DI (dependency injection)? Because I don't want to write those log() or progress() methods functions more than one time. Besides I can't use a progress function in a webapplication. One reason is I don't know how to to that (if its possible with php here), but this would be another question.
Thanks in advance.
As a PHP programmer you should be aware of and use the PSR. In this case you should use dependency injection and the LoggerInterfaces.
For web application you should configure your composition root to use a logger implementation that logs to a file. For console application you should log to the terminal.
The composition root is the place where you configure your Dependency Injection Container (DIC). See more about Yii DIC here.
In order to do that you should be able to switch between these two composition roots by an environment variable or by php_sapi_name.

Load many php-files within controller and situational call them

I have another question related to a project of mine. Consider the following scenario.
Scenario
I want to build a Slack App from scratch (to learn something about it)
it will be used for a Slash Command and to send daily updates to a given channel at a given time (via cronjob). So I will most probably provide a config.json and add the modules I want to call when the cronjob triggers the bot.
The bot class builds the bot and also sets up a sendToSlackfunction (does the curl stuff)
In the listen function the bot listens to certain keywords (eg. keyword1) and then execute a certain function. This function gets data from an API. Each keyword he can listen to has its own API-logic.
It would probably be easy to build it with Botkit and node, but I want to stick to PHP and do it on my own.
My questions
How can I load multiple modules (eg. from the /modules directory) in my Slack-App controller (eg. controller.php)?
What is the best way to then "loop" through all the modules I have loaded and if the module says "Hey that is me" (in other words keyword === module.keyword) execute their main function. This main function will get the results from the API and return it back to the controller. The controller will append it to the $outputText variable and at the end use the sendToSlack function to send it to Slack.
Puh, I hope this is it and I did not forget something so far. Do you - hopefully - know what I am talking about? I am happy to answer questions you have and hopefully you can help me.

Codeigniter - find out if class was called directly

I am attempting to create a controller that can detect if it is called from another controller in the application.
If it called directly via the URL, however, I need to know so I can perform some other actions.
i.e.
cheese/modulename calling potato/modulename is different to someone accessing site/cheese/modulename via URL - and I need to be able to pick up on this and act accordingly.
I am aware of:
$this->router->class
but it will not work as I may have the same named class in another module (HMVC pattern as an FYI) that may want to call this controller (cheese/modulename calling potato/modulename as an example would return 'modulename' - so I can't use that as a check to see if it was called by itself.)
I basically need a check for:
controller was called via another controller = true / false
can anyone tell me how (or if I am being thick!)
I am doing this in the __construct() just in case your solution will have a problem with that (can't see why but you never know!)
EDIT
Thank you to Mohammad Walid for his answer.
For clarity the structure is
CLIENTS
MODELS
CONTROLLERS
- Client
- Api
VIEWS
JOBS
MODELS
CONTROLLERS
- Jobs
- Api
VIEWS
I will be calling the API from Client - but may also call it from another API (possibly) That may be
In another Module
For Example the CLIENTS Api might get called from the JOBS Api Controller (I have no intention of doing this at present but it may be a possibility under different scenarios I haven't forseen and want to make it future-proof so I don't have a massive refactoring job in the future.)
You can try this:
function is_called_via_url($controller_object){
return $this->uri->segment(1) === get_class($controller_object);
}
and in your controller:
if(is_called_via_url($this)){
//do something
}
I'm not quite sure if passing $this as an argument in the constructor will work, but it worth try.
Reference and a hint from MonkeyZeus's comment.
From the comments there seems to be no way to do this without using debug_backtrace($options, $limit)
However the work-around I have ended up doing is to add a 'flag' within the authorisation module (which is called before all controllers)
The flag defaults to false.
If a controller from within my application calls the API page I turn this flag to true (is_application = true - I am currently just manually pasting this into the __construct of any controllers in my application that need to talk to my API)
I can then just do a simple
if(!is_application){
//this was called directly from the URL not from within the application
}
from within the API controller.
Hopefully this will be helpful for others who are attempting this sort of thing and once again thank you to the people who took the time to comment / answer.

how to process stored requests in CI

I have been using a controller method post directly to perform some db and social network operations but im finding a few points of failure between it and the hardware — so I came up with the idea of storing all the request in a db table to be used as a queuing system instead so I can process them in my own time rather than real time
The thing I'm struggling with now is handling my requests . I know this isn't very MVC — but its quick fix.
How do I call another controller's method from within my process queue method? I have tried including the file and instantiating it — then passing it the variables i would have done from the web.
function process(){
$result = $this->mque->get_all();
include('post.php');
$get = new post();
foreach($result->result_array() as $item){
$get->index($item['rfid'],$item['station_id'],$item['item']);
}
}
but i get an error- when i call the normal index method- it runs fine but i get an undefined method error when call it through the instantiated class method- (this is my problem)
Message: Undefined property: post::$db
The why
I am setting the process queue method to run based on a cron job running at a set interval of time.
Originally everything ran to index method of post — but since post::index() can take 10-15 seconds to run and the reader is not multi threaded — someone could use the reader within 7 seconds and the script wouldn't have run completely.
Is there a better way of doing this rather than using my current process method?
update
there is two ways to do this- either use php to fopen/get from the web
or do it sprogramming using $class->method()- i would prefer to do this the first method but dont really see any option with the error i mentioned before
That's easy: you don't have one controller call another. As a rule, if you need something to exist in two different places, you have two options:
Have them both subclass the same object
pro: That way the method is already there
con: You can only subclass one thing, and you have to build your own class loading system (NOT GOOD)
Have a library (or model) which they both share
pro: The method can then be tested better (it is (or it was at one point) easier to unit test models than it is to test controllers), the code can be shared without a custom class-loading syntax.
con: This may involve a little refactoring (but it should be as easy as moving the code from the controller's method to a library's method and then simply calling the library in the public controller method).
Either one of those would solve your particular problem. Personally, because of how CI loads controllers, my preference is to create libraries.
CodeIgniter: Load controller within controller
Is this something that could help you out quickly? Check the bottom reply.

Automate database table creation from within CakePHP framework

I'm trying to write a webapp with CakePHP, and like most webapps I would like to create an installer that detects whether the database has been initialized and, if not, executes the installation process.
This process will be entirely automated (it assumes the database itself already exists, and that it is granted full administrative access through the anonymous account with no password...this is for a sandbox environment, so no worries about security), so it needs to be able to detect (regardless of the request!) if the database tables have been created and initialized, and if not, to perform that initialization transparently and then still serve up the user's original request.
I considered writing a sort of Bootstrap controller through which all requests are routed, and a single SQL query is run to determine if the database tables exist, but this seemed cumbersome (and the controller requires a corresponding model, which needn't be the case here). The other possibility is was to override AppModel and place within it the same test, but I was unsure how to do this, as there isn't any documentation along these lines.
Thanks in advance!
tl;dr version: What is the CakePHP equivalent (or how can the equivalent be written for CakePHP) of a J2EE servlet's "init()" method?
Check out the AppError class - may be there is a missing database table error that your can override and run your create database table SQL from there?
Something like this might do what you're looking for, but does require an extra query per model.
<?php
class AppModel extends Model {
var $create_query = false;
function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
if (!empty($this->create_query)) {
$this->query($this->create_query);
}
}
}
?>
Then you would just need to add var $create_query = 'CREATE TABLE IF NOT EXISTS ...;` in your models. The MySQL CREATE TABLE documentation has more information on IF NOT EXISTS.
However, I'm not certain that this isn't trying to call query() too early. Or before the check to see if the table already exists, which it would have to be. Otherwise, CakePHP would error out instead of creating your table. There isn't much documentation on the subject and your best bet for more information going to be to take a look at cake/libs/model/model.php directly.
Update: The code above would not work as written.
After looking into the Model class a little deeper, Model::__construct calls Model::setSource(). Model::setSource() checks to see if the table exists and throws an error if it doesn't. To use this method, that's where you'd have to override and insert the query. The code below may need to differ, depending on which version of CakePHP you're using.
<?php
function setSource($tableName) {
// From Model::setSource(). I believe this is needed to make query() work.
$this->setDataSource($this->useDbConfig);
// Create our table if we have a create query
if (!empty($this->create_query)) {
$this->query($this->create_query);
}
// Now call the parent
parent::setSource($tableName);
}
?>
Do you really want to make every request check that the database exists? This seems terribly wasteful.
The first time it checks it will set up the database, and then for every one of the next million requests it will check again even though the database has of course by this time been set up!
It's a better idea to require that whomever installs your application do some setup. You should provide a separate script that is not run during every request, but is run manually by the person during installation.
Re your comment about the best way to do this with CakePHP, have you looked at Cake's support for database schema migrations?
http://book.cakephp.org/view/734/Schema-management-and-migrations
I thought about doing this at one point in time, and came up with a good way to do it.
First off, a note: I believe your app needs write access to app/config/database.php, so you can write the correct information. I have a workaround, but let me explain the method by which the installer works.
PHP Scripts can rewrite themselves (or delete themselves) on the fly. Since the script is loaded into memory and THEN the bytecode is executed, it isn't dependent upon the file once it is done working. So you could theoretically have something that overrides the AppError class to run a specific controller (say your SetupWizardController) with all the regular, CakePHP stuff loaded. Once you go through the entire wizard, write the database.php file/setup anything else needed, overwrite the AppError and AppController files (I can see you needing the AppController to work differently if the app hasn't been installed in certain cases) with ones that are stored somewhere like in app/vendors/myapp. The script has finished executing and now your checks will not occur on subsequent runs.
This would get rid of the problem Bill Karwin brought up, utilize AppError, and make an installer.
The other way to do it is to include the idea of Environments in your application. Say you store your environments in app/config/environment.php. This file would be given write access to (and that access would be removed at the end of setup), or could be overwritten. Regardless, it would exist whenever someone attempts to deploy your application, and database.php automatically imports the databases from the environment.php file. The constructor of the database.php class would then overwrite it's $default database with the one specified in environment.php. The $default in database.php would reference a NoDB Datasource, which you can check in the AppController (I think you would be able to instantiate a dummy model and check it's datasource), and then push up the required Installer. Obviously you'd overwrite the AppController after this as well.

Categories