I've taken over an old CakePHP 2.X website and have no prior experience with CakePHP so please forgive me if this is a stupid question.
I was looking at making some changes to some vendor files and noticed that we appear to have multiple copies of various files (which are, for the most part, identical) in 2 different places:
app/webroot/api/vendor/API/lib/
vendors/API/lib/
Additionally I noticed there are several other vendor directories in various other places.
I am using App::import('Vendor', 'example', array('file' => 'API/lib/example.php')); to load the scripts in question.
Could someone please explain to me what the best practices are regarding file structure relating to vendor files? Additionally, am I safe to delete the duplicate copy of all the files? How does CakePHP know which copy to load?
Edit:
I have come to the conclusion that the files are being loaded from vendors/API/lib/ rather than app/webroot/api/vendor/API/lib/, is it possible that the files in the latter location are redundant? I cannot seem to find any references to them.
Well as Sudhir has commented you, there is a folder in your app project which is called Vendor. I would recommend you to put it there.
app > Vendor
For example, I have created a folder called Csv for generating my own csv files through a Shell which is launching them. It is located inside app > Vendor > Csv
For importing this to my projects, I did the next for being able to use it:
<?php
include('GenericShell.php');
require_once(ROOT . DS . 'app' . DS . 'Vendor' . DS . 'Csv' . DS .
'CsvGenerator.php');
class CsvPatientsShell extends GenericShell {
That's one only example with PHP.
One other one would be, if in this case you have a Component which is called component.php and you want to import it to a Controller which you use frequently inside your project :
Component would be located into
Controller > Component > Namecomponent.php
The next thing you would have to do would be to do the import likewise inside your controller:
Let's say your controller's name is NameController.php and is located inside the Controller folder.
Controller > NameController.php
public function main_function() {
App::import('Component', 'Namecomponent');
$NameComponent = new NameComponent();
$this->layout = null;
$this->autoLayout = false;
die();
}
That would be a more correct way to do it with CakePhp but both mentioned are legit I'd say.
I hope that will help you somehow.
My website is with a hosting provider that has the MessageFormatter class available on the server (Linux, PHP 7.0.27) but it is an old ICU version (4.2.1) that doesn't support my message {number,plural,=0{# available} =1{# available} other{# available}} and gives the error:
Message pattern is invalid: Constructor failed
msgfmt_create: message formatter creation failed: U_ILLEGAL_CHARACTER
...because of the =1 and =2 notation.
I'm not able to make changes to the server so how can I force using the fallback method provided by Yii2 which works just fine?
There is this hacky way you can try.
Copy the yii\i18n\MessageFormatter code to a new file. Name it MessageFormatter.php and place somewhere in your application (but not in vendor folder).
In this new file change the format() method to:
public function format($pattern, $params, $language)
{
$this->_errorCode = 0;
$this->_errorMessage = '';
if ($params === []) {
return $pattern;
}
return $this->fallbackFormat($pattern, $params, $language);
}
Don't change anything else (including namespace).
Now let's use Yii mapping.
Find a place in your application when you can put code that will be run every time in bootstrapping phase. Good place for this is common/config/bootstrap.php if you are using "Advanced Template"-like project.
Add there this line:
Yii::$classMap['yii\i18n\MessageFormatter'] = 'path/to/your/MessageFormatter.php';
Obviously change the path to the one you've chosen. Now Yii autoloader will load this class from your file instead of the original Yii vendor folder (as mentioned in Class Autoloading section of the Guide).
In the modified file MessageFormatter method presence of intl library is never checked so fallback is used as default.
The downside of this trick is that you need to update manually your file every time original Yii file is changed (so almost every time you upgrade Yii version).
Another approach is to configure I18N component in your application to use your custom MessageFormatter where you can extend the original file and just override format() method inside without modifying class map.
The issue is, I would like to use all benefits of tasks runner and bower into my existing Zend-Project. I'm not sure, how should I restructure my whole project folder.
My current structure looks like follows:
config
module
vendor
public
css
js
libs
jquery
bootstrap
controller1
jsController1.
etc
index.php
From this (Gulp and Bower - creating proper files structure) I know, that I have to create a separate folder to save all libraries installed from bower and then I have to copy them with gulp into the public folder. Now depending on enviroment (Production or Development) I want to use minified css and js scripts. So should I create 2 public - folders and depending on enviroment change base path for zend? Or what's the best way to perform that?
Additionally I would like to integrate browser-sync into this project (cause of livereload). Therefore I want to use gulp-connect to start the php-server. But then the enviroment variables from apache are not set. How can I set it? According to documentation I have to add newArgs (cause my Options are array) "APPLICATION_ENV=development". But if add this after comma, i get error: "Could not open input file: APPLICATION_ENV=development"
My currenty gulfile:
var gulp = require('gulp'),
php = require('gulp-connect-php');
gulp.task('php', function() {
php.server({
configCallback: function _configCallback(type, collection) {
// If you wish to leave one of the argument types alone, simply return the passed in collection.
if (type === php.OPTIONS_SPAWN_OBJ) { // As the constant suggests, collection is an Object.
// Lets add a custom env var. Good for injecting AWS_RDS config variables.
collection.env = Object.assign({
APPLICATION_ENV: "development"
}, process.env);
return collection;
} else if (type === php.OPTIONS_PHP_CLI_ARR) { // As the constant suggests, collection is an Array.
let newArgs = [
'-e', // Generate extended information for debugger/profiler.
'-d', 'memory_limit=2G' // Define INI entry, Up memory limit to 2G.
,"APPLICATION_ENV=development"
];
// Ensure our argument switches appear before the rest.
return newArgs.concat(collection);
}
}
,
base: 'public',
port: 8010,
keepalive: true},
function _connected_callback() {
console.log("PHP Development Server Connected.");
});
}
);
I wrote custom classes and want to use them in pimcore application.
I took them to /website/lib/Custom directory on server. Afterwards, I wrote recursive script includer for each Class located in the directory and included that script in /index.php file.
It is absolutely not pimcore standard but it works.
In pimcore/config/startup.php exists snippet:
$autoloaderClassMapFiles = [
PIMCORE_CONFIGURATION_DIRECTORY . "/autoload-classmap.php",
PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY . "/autoload-classmap.php",
PIMCORE_PATH . "/config/autoload-classmap.php",
];
$test = PIMCORE_ASSET_DIRECTORY;
foreach ($autoloaderClassMapFiles as $autoloaderClassMapFile) {
if (file_exists($autoloaderClassMapFile)) {
$classMapAutoLoader = new \Pimcore\Loader\ClassMapAutoloader([$autoloaderClassMapFile]);
$classMapAutoLoader->register();
break;
}
}
I guess that this provides inclusion of all those classes put into returning array from autoload-classmap.php.
Having in mind that /pimcore/config/autoload-classmap.php exists, the mentioned loop would break at first iteration so classes that I would put into custom autoload-classmap are not going to be included in project.
My question is can I change files from /pimcore directory and expect that everything would be fine after system update?
No, you should not overwrite anything in the pimcore directory, since the files in there get overwritten by the update mechanism.
You can do what you want by using the /website/config/startup.php which will not get overwritten:
https://www.pimcore.org/wiki/display/PIMCORE4/Hook+into+the+startup-process
But instead of loading all your classes as you did, take advantage of the autoloader by adding this to the /website/config/startup.php:
// The first line is not absolutely necessary, since the $autoloader variable already gets
// set in the /pimcore/config/startup.php, but it is a more future-proof option
$autoloader = \Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Custom');
If you are properly using namespaces and naming your files correctly that's all you need to do.
I am trying to write a cronjob controller, so I can call one website and have all modules cronjob.php executed. Now my problem is how do I do that?
Would curl be an option, so I also can count the errors and successes?
[Update]
I guess I have not explained it enough.
What I want to do is have one file which I can call like from http://server/cronjob and then make it execute every /application/modules/*/controller/CronjobController.php or have another way of doing it so all the cronjobs aren't at one place but at the same place the module is located. This would offer me the advantage, that if a module does not exist it does not try to run its cronjob.
Now my question is how would you execute all the modules CronjobController or would you do it a completly different way so it still stays modular?
And I want to be able to giveout how many cronjobs ran successfully and how many didn't
After some research and a lot procrastination I came to the simple conclusion that a ZF-ized cron script should contain all the functionality of you zend framework app - without all the view stuff. I accomplished this by creating a new cronjobfoo.php file in my application directory. Then I took the bare minimum from:
-my front controller (index.php)
-my bootstrap.php
I took out all the view stuff and focused on keeping the environment setup, db setup, autoloader, & registry setup. I had to take a little time to correct the document root variable and remove some of the OO functionality copied from my bootstrap.
After that I just coded away.. in my case it was compiling and emailing out nightly reports. It was great to use Zend_Mail. When I was confident that my script was working the way I wanted, I just added it my crontab.
good luck!
For Zend Framework I am currently using the code outlined bellow. The script only includes the portal file index.php, where all the paths, environment and other Zendy code is bootstrapped. By defining a constant in the cron script we cancel the final step , where the application is run.
This means the application is only setup, not even bootstrapped. At this point we start bootstraping the resources we need and that is that
//public/index.php
if(!defined('DONT_RUN_APP') || DONT_RUN_APP == false) {
$application->bootstrap()->run();
}
// application/../cron/cronjob.php
define("DONT_RUN_APP",true);
require(realpath('/srv/www/project/public/index.php'));
$application->bootstrap('config');
$application->bootstrap('db');
//cron code follows
I would caution putting your cronjobs accessible to the public because they could be triggered outside their normal times and, depending on what they do, cause problems (I know that is not what you intend, but by putting them into an actual controller it becomes reachable from the browser). For example, I have one cron that sends e-mails. I would be spammed constantly if someone found the cron URL and just began hitting it.
What I did was make a cron folder and in there created a heartbeat.php which bootstraps Zend Framework (minus MVC) for me. It checks a database which has a list of all the installed cron jobs and, if it is time for them to run, generates an instances of the cron job's class and runs it.
The cron jobs are just child classes from an abstract cron class that has methods like install(), run(), deactivate(), etc.
To fire off my job I just have a simple crontab entry that runs every 5 minutes that hits heartbeat.php. So far it's worked wonderful on two different sites.
Someone mentioned this blog entry a couple days ago on fw-general (a mailinglist which I recommend reading when you use the Zend Framework).
There is also a proposal for Zend_Controller_Request_Cli, which should address this sooner or later.
I have access to a dedicated server and I initially had a different bootstrap for the cron jobs. I eventually hated the idea, just wishing I could do this within the existing MVC setup and not have to bother about moving things around.
I created a file cron.sh, saved is within my site root (not public) and in it I put a series of commands I would like to run. As I wanted to run many commands at once I wrote the PHP within my controllers as usual and added curl calls to those urls within cron.sh. for example curl http://www.mysite.com/cron_controller/action Then on the cron interface I ran bash /path/to/cron.sh.
As pointed out by others your crons can be fired by anyone who guesses the url so there's always that caveat. You can find a solution to that in many different ways.
Take a look at zf-cli:
scripts at master from padraic/ZFPlanet - GitHub
This handles well all cron jobs.
Why not just create a crontab.php, including, or requiring the index.php bootstrap file?
Considering that the bootstrap is executing Zend_Loader::registerAutoload(), you can start working directly with the modules, for instance, myModules_MyClass::doSomething();
That way you are skipping the controllers. The Controller job is to control the access via http. In this case, you don't need the controller approach because you are accessing locally.
Do you have filesystem access to the modules' directories? You could iterate over the directories and determine where a CronjobController.php is available. Then you could either use Zend_Http_Client to access the controller via HTTP or use an approach like Zend_Test_PHPUnit: simulate the actual dispatch process locally.
You could set up a database table to hold references to the cronjob scripts (in your modules), then use a exec command with a return value on pass/fail.
I extended gregor answer with this post. This is what came out:
//public/index.php
// Run application, only if not started from command line (cli)
if (php_sapi_name() != 'cli' || !empty($_SERVER['REMOTE_ADDR'])) {
$application->run();
}
Thanks gregor!
My solution:
curl /cron
Global cron method will include_once all controllers
Check whether each of the controllors has ->cron method
If they have, run those.
Public cron url (for curl) is not a problem, there are many ways to avoid abuse. As said, checking remote IP is the easiest.
This is my way to run Cron Jobs with Zend Framework
In Bootstrap I will keep environment setup as it is minus MVC:
public static function setupEnvironment()
{
...
self::setupFrontController();
self::setupDatabase();
self::setupRoutes();
...
if (PHP_SAPI !== 'cli') {
self::setupView();
self::setupDbCaches();
}
...
}
Also in Bootstrap, I will modify setupRoutes and add a custom route:
public function setupRoutes()
{
...
if (PHP_SAPI == 'cli') {
self::$frontController->setRouter(new App_Router_Cli());
self::$frontController->setRequest(new Zend_Controller_Request_Http());
}
}
App_Router_Cli is a new router type which determines the controller, action, and optional parameters based on this type of request: script.php controller=mail action=send. I found this new router here: Setting up Cron with Zend Framework 1.11
:
class App_Router_Cli extends Zend_Controller_Router_Abstract
{
public function route (Zend_Controller_Request_Abstract $dispatcher)
{
$getopt = new Zend_Console_Getopt (array());
$arguments = $getopt->getRemainingArgs();
$controller = "";
$action = "";
$params = array();
if ($arguments) {
foreach($arguments as $index => $command) {
$details = explode("=", $command);
if($details[0] == "controller") {
$controller = $details[1];
} else if($details[0] == "action") {
$action = $details[1];
} else {
$params[$details[0]] = $details[1];
}
}
if($action == "" || $controller == "") {
die("Missing Controller and Action Arguments == You should have:
php script.php controller=[controllername] action=[action]");
}
$dispatcher->setControllerName($controller);
$dispatcher->setActionName($action);
$dispatcher->setParams($params);
return $dispatcher;
}
echo "Invalid command.\n", exit;
echo "No command given.\n", exit;
}
public function assemble ($userParams, $name = null, $reset = false, $encode = true)
{
throw new Exception("Assemble isnt implemented ", print_r($userParams, true));
}
}
In CronController I do a simple check:
public function sendEmailCliAction()
{
if (PHP_SAPI != 'cli' || !empty($_SERVER['REMOTE_ADDR'])) {
echo "Program cannot be run manually\n";
exit(1);
}
// Each email sent has its status set to 0;
Crontab runs a command of this kind:
* * * * * php /var/www/projectname/public/index.php controller=name action=send-email-cli >> /var/www/projectname/application/data/logs/cron.log
It doesn't make sense to run the bootstrap in the same directory or in cron job folder. I've created a better and easy way to implement the cron job work. Please follow the below things to make your work easy and smart:
Create a cron job folder such as "cron" or "crobjob" etc. whatever you want.
Sometimes we need the cron job to run on a server with different interval like for 1 hr interval or 1-day interval that we can setup on the server.
Create a file in cron job folder like I created an "init.php", Now let's say you want to send a newsletter to users in once per day. You don't need to do the zend code in init.php.
So just set up the curl function in init.php and add the URL of your controller action in that curl function. Because our main purpose is that an action should be called on every day. for example, the URL should be like this:
https://www.example.com/cron/newsletters
So set up this URL in curl function and call this function in init.php in the same file.
In the above link, you can see "cron" is the controller and newsletters is the action where you can do your work, in the same way, don't need to run the bootstrap file etc.