Zend framework 1.12 front controller configuration - php

I am trying to register my modules with the front controller using the bootstrap class by using the code below:
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
This works fine and all the modules are registered. But when I do the following my module directory is not registered and I get the "controller not found error".
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
As the front controller implements the singleton design pattern shouldn't both the code blocks refer to the same instance and both of my code blocks should work?

The issue is this line in your application.ini:
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
The file structure you gave doesn't include this directory, so I think you can remove it and it will solve your problem.
Longer explanation of why it works one way and not the other:
There are two ways of configuring resources with Zend Application: class resources and plugin resources. The resources.xxx lines in application.ini are plugin resources, and the _init methods are class resources. Class resources run before plugin resources, but if you run $this->bootstrap('...') it will ensure that resource has been initialized (regardless of type).
So in your first code example, $this->bootstrap('FrontController'); triggers the frontController resource which you defined in your application.ini. This then sets the controller directory using the path you gave it. You then add a module directory which seems to be what your application actually uses.
In your second code example, you get an instance of the front controller and then add your module directory to it (all good). But then the plugin resource will run (as remember these run after). This will grab your existing front controller instance, but setting the controller directory overrides the module directory, so this is what causes your error later on.
I generally try and avoid mixing and matching plugin and class resources as it can cause some weird problems. So either put it all in application.ini or all in the Bootstrap class. Personally I find the latter more readable.
If you do need the controller directory as well, adding $this->bootstrap('FrontController'); to the start of your second code example should work as well, since that will trigger the plugin resource.

Here is what is going on. Tim Fountain is correct. The following line in your application.ini file is the culprit. If you remove it, your application should load correctly.
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
You may also have to remove this line since it is part of the frontcontroller too.
resources.frontController.params.displayExceptions = 1
But you also have 2 other options to be able to use Zend_Controller_Front::getInstance()
Option 1. You change your index.php to bootstrap specific resources:
$application->bootstrap(array('FrontController', 'ModuleConfig'))->run();
This will bootstrap your FrontController first from your application.ini and then run your initModuleConfig method. Essentially this allows you to control what resources are loaded and in what order. This is helpful when you have resources that you only want to bootstrap at specific times.
I think if you do not provide an array to the bootstrap method here, then it will call all methods prefixed with init in the order they are declared.
Option 2. You could do the configuration of your of your module directory within your application.ini
resources.frontController.moduleDirectory = APPLICATION_PATH "/modules"
resources.frontController.params.prefixDefaultModule = 1
resources.frontController.params.displayExceptions = 1
This will do what you were trying to do in code. One thing to note is that you may want to bootstrap your FrontController in the constructor of your Bootstrap class. This is just in case you need to use it during your custom initialization.
Now here is an explanation for why option 1 works.
This is a method from Zend_Application_Bootstrap_BootstrapAbstract
protected function _bootstrap($resource = null)
{
if (null === $resource) {
foreach ($this->getClassResourceNames() as $resource) {
$this->_executeResource($resource);
}
foreach ($this->getPluginResourceNames() as $resource) {
$this->_executeResource($resource);
}
} elseif (is_string($resource)) {
$this->_executeResource($resource);
} elseif (is_array($resource)) {
foreach ($resource as $r) {
$this->_executeResource($r);
}
} else {
throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__);
}
}
This _bootstrap is called when you call the public bootstrap method. Example $this->bootstrap("FrontController") or $this->bootstrap();
Notice the case when you don't pass in a parameter. This will call the null case which is the case you get in your index.php - $application->bootstrap()->run();
First it will load your class resources and then it will also call your plugin resources. Notice that the the plugin resources are not loaded in the other cases.
If you follow the method calls for class resources, it is basically call your init methods in your bootstrap call class.
The plugin resources are called afterward and one of the plugin resources. I'm not entirely sure how all the plugin resources are loaded, but I believe one source is in your application.ini file. These will be the lines that start with resources. Examples includes views, frontcontroller, db.
So in your situation where you call $application->bootstrap()->run();, your init methods are loaded first. But your FrontController is not bootstrapped yet. It eventually gets bootstrapped as a plugin resource which is taken from your application.ini. This apparently overwrites what you did in your bootstrap class.
Another question you may be asking is why is the FrontController instance not overridden when you call $this->bootstrap("FrontController) explicitly. I guess this pretty obvious but personally I had this question myself.
In the Bootstrap class, there is a method called _executeResource and this will check if the resource has already been bootstrapped. It uses an associative array to keep track. The associative array is called $this->_started.
This is why the plugin resource for the front controller is not called in your first case where you explicitly bootstrap the front controller. Hence your front controller instance is not replaced.

You need to bootstrap the front controller first so it is initialized. Once done in the cycle you could always retrieve it with the static getInstance.

Related

Codeigniter 3 - Third_party and controllers / loading ressources

I'd like to use the controllers from the /third_party/myapp/controllers folder without having to move them to the /application/controllers one
It doesn't seem possible (at least not without hacking the core). Though the question concerns loading model, limitations are mentioned here
I don't feel like using HMVC - as stated in the solution proposed there, cause I don't need any other functionality than avoiding to multiply file transfer in CI base folders (i don't really need the 'hierarchical' part of it)
But I don't really get it... if one declares its third_party app, CI will load resources from those folders (as stated by the doc)
config/
helpers/
language/
libraries/
models/
[metaphysical] Why wouldn't it be possible to simply load controllers as well ?
[pragmatic] is it a good idea to try to hack the core system or should I stay on the "HMVC or do copy your files" choice ?
[optimistic] do someone have a solution ?
EDIT
While looking further to trajchevska tip in another installation of CI, I tried to implement DFriend example of spl_register. I read several times the manual and this answer as well :
So far I understood that spl_autoload_register is triggered when a class is called, which allows to skip their manual loading
So I've tried simply adding the spl_autoload_register at the end of the config file in application/config/config.php, expecting it to print Auto load ClassName when a first class is called - a bit naïve, I know.
but from what I understood (thanks to DFriend), this spl_autoload_register would NOT work with controllers, as CI will always check if the file exists (in its own path) before trying to declare the controller.
It seems on the other hand that it could work with other classes (core, model ... see DFriend answer below)
Conclusion
So, after a few hairs pulled out of my head, I decided to try the HMVC extension for CI - MX from WireDesign. It solved my problem of loading any resources from different folder, along with bringing a brand new scope of shared classes and their attached issues (what is available where ?).
As it's the beginning the project I had the opportunity to switch to the "hierarchical" side, otherwise I'd followed Dfriend's advice of
going with the flow and putting controllers for third-party packages
where CI expects them to be
You could use PHP's spl_autoload_register to implement an autoloader. A google search will reveal multiple approaches for using one within the CI framework.
One that works and is often described is to register the autoloader in config.php or maybe a site-specific constants file. Such a solution is to put something like this in one of the mentioned files.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
});
You can use hooks if you want to avoid hacking config.php or some other file. Here's one way to do that.
application/config/config.php
$config['enable_hooks'] = TRUE;
application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
}
Addendum
On further digging and consideration the above will not work because CI insists that Controllers be in the application/controllers directory or a subdirectory thereof.
PHP will run a registered autoloader when you try to use a class/interface which hasn’t been defined yet. Controllers are php classes so you might expect an autoloader to come to the rescue. But CI is very explicit about where it looks for controller files. If the file is not found then it never attempts any calls to the class. Instead it abandons all hope and immediately issues an 404 error. The autoloader never gets a chance to look for the file.
The answer to the metaphysical question is that CI's routing and URL parsing are tightly coupled to the file storage structure making the desired functionality impossible. (without some serious core hacking)
The answer to the pragmatic question is very subjective. I'm not fond of the HMVC approach and personally would opt for going with the flow and putting controllers for third-party packages where CI expects them to be.
Start: Serious diversion from question at hand
So, what good is registering an autoloader in the CI environment?
Controllers are defined like so.
class Home extends CI_Controller{ ...
The class being extended, CI_Controller, is a core class. If you want to extend this core class for use by multiple other controllers you can use the CI convention of prepending the class name with "MY_". e.g.
class MY_Controller extends CI_Controller
(
//class implementation
}
and then use it to define actual controllers using the extended core class
class Home extends MY_Controller{ ...
The problem is that you can only extend the core CI_Controller once using this convention. What if you have a need for more than one extension to the CI_Controller core class? One solution is registering an autoloader. (A good discussion of this and other ways to overcome the MY_Controller monopoly is HERE.)
An autoloader for these purposes could look this.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
}
}
Note that the above also allows you to extend models from other models.
End: Serious diversion from question at hand
I assume it's because of the routing process - the CI Router checks whether a corresponding controller exists in the application folder, or a respective route is defined in the config. If none of them is found, 404 is returned.
You can override the Router file to enable loading controllers from other locations. In the core folder, create a MY_Router file that will extend the CI_Router. Then extend the _validate_request function, to allow loading controller files from different path than the default.
UPDATE: So, I've spent some time on this and didn't manage to extend the Router to get the controller file from the third party folder. It's easy to do when it comes to subfolders of the default controllers directory, but setting up a custom directory for reading the controllers is not that straightforward and unfortunately, I couldn't find any documentation on that.

Override of Zend_View_Helper not being picked up in Zend_Layout

TL;DR: In ZF1, after registering a custom prefix in application.ini, the Zend_View_Helper_* namespace takes precedence over my own despite working correctly in regular views.
Created an override of Zend_View_Helper_Url, called App_View_Helper_Url.
Placed the file in APPPLICATION_PATH/library/App/View/Helper/Url.php
Registered the namespace via application.ini: resources.view.helperPath.App_View_Helper = APPLICATION_PATH "/library/App/View/Helper"
This works. In my Views, the custom URL helper is now being called. However, in template views, the additional namespace doesn't seem to take hold. I verified this by running this snippet: <?php var_dump(get_class($this->getHelper('url'))); ?>
index.phtml: App_View_Helper_Url
layout.phtml: Zend_View_Helper_Url
I've tried resolving this by explicitly setting the Layout view to the View resource being bootstrapped - this did not work.
$this->getResource('layout')->setView($this->getResource('view'));
In the end I fixed it by fixing doing the following in the Bootstrap:
$view = $layout->getView();
$view->addHelperPath(APPLICATION_PATH . '/library/App/View/Helper', 'App_View_Helper');
This solution is not very satisfying as I'm now defining the same helper path twice, leading to potential breakage down the line. So while I have a workaround, I'd prefer to understand how/where to configure the Zend_View instance used by Zend_Layout correctly, preferably through application.ini.

How to call a zend controller action from an independent php file?

I am having a controller IndexController.php in which action is something like this
class IndexController extends CustomControllerAction {
public function preDispatch() {
if (!$this->view->authenticated) {
$this->_redirect('/users/login');
}
}
public function indexemailAction() {
//somecode which calculates certain things
}
}
NOw,I need to call the action "indexmailAction" inside the IndexController.php with an independent php file
The php file is indextest.php
<?php
//Need to write some code to call indexmailAction in IndexController.php
?>
What should I write in this file ......
Thanks in advance
I know this is a few years old, and this may not be the intended use of the classes/functions, but I've found the following quite useful in isolated files that are called from the command line.
The problem this solves for me is that it eliminates spawning of Apache processes. The solution is great because I can access the some Controller/Action needed that I would from the URL.
In almost any ZF1 based app, you can copy your index file and keep everything the same and just comment out the following line.
$application->run();
Anything below this line you can access with your autoloaders etc. It's crude, but it works. Unfortunately, you'll soon find yourself with limited access to a lot of the files your application has, and the feeling the only way you can access the files needed is through a Controller/Action.
Instead, I use the following in a new file below $application->bootstrap() ( still removing the $application->run() ):
$front = Zend_Controller_Front::getInstance();
// You can put more here if you use non-default modules
$front->setControllerDirectory(array(
'default' => APPLICATION_PATH.'/controllers'
));
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setNeverRender(true);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
$req = new Zend_Controller_Request_Http("http://anydomain.tld/controller/action");
// Example just to see how this can be extended
$req->setParam("someVar", "someValue");
$front->setRequest($req);
$front->dispatch();
In the end you have a isolated PHP file that bootstraps everything the same as your main index.php for the web, but you can manually trigger a controller/action as needed, giving you easier access to the rest of the files with how ZF1 intended you to access them.
Controllers are designed to be used in an MVC, not by scripts. Your controller should assemble request variables, direct them to models and return an HTTP response of some sort. Your scripts should act directly on the models instead.
Anyhow, if you insist, you can instantiate a controller class and call methods just like any other class as long as you inject any dependencies that the MVC would have.
If you want logic used in multiple places in your actions, then it should go in an action helper or if very generic code, then in a custom library (/library/custom/)
NB: The authentication would be better suited in a plugin rather than the pre-dispatch method in every controller.
You should not have to call a controller action for this, your logic should reside in your models. Then you can create a new instance of your model and invoke the appropriate methods. example :
require_once '/path/to/mymodel.php';
$mymodel = new Mymodel();
$data = $mymodele->fetchAll();
PS: Maybe you should think of creating a restful api to handle calls from outside your application
UPDATE:
ok, I see now what you need, The best way to achieve it is to call a url instead of a file (e.g. website.com/emails/send), if you are worried about security you can use some preshared key to make sure the request comes from you, send it with the request and check if it's correct in your action.

Circular resource dependency error in Zend

I have a bootstrap class which I want to use to set CSS variables:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$this->bootstrap('view');
...
...
}
}
But trying to get the view resource fails at the bootstrap('view') stage. I get the error:
... Circular resource dependency detected' in C:\ZendFramework\library\Zend\Application\Bootstrap\BootstrapAbstract.php on line 662
...
Which is strange because this is the procedure that tutorials(and the zend documentation) use. What could be wrong?
Change the method to something like _initViewStuff() and all will be fine.
The reason is that the bootstrap sequence in Zend_Application_Bootstrap_BootstrapAbstract is as follows:
Your initial call to $app->bootstrap() in public/index.php runs through all _initXxx() methods (#see Zend_Application_Bootstrap_BootstrapAbstract::getClassResourceNames()) and calls $this->bootstrap('xxx') for each Xxx it finds. It will then do a similar thing for all the plugin resources defined by resources.* keys in application.ini (though yours never gets that far, as described below).
The call to bootstrap('view') internally stores a flag that he has started the process to bootstrap a resource called view.
He does a similar thing as (1), looking for a matching _initXxx() method. He find it and attempts to execute $this->_initView()
He notices the flag he set, indicating that he's gonna hit an infinite loop, so he bails out with circular dependency exception.
Typically, for each resource xxx, you bootstrap it using one (but not both, as you have discovered) of the following approaches:
Define an _initXxx() method.
Creating a plugin resource class named something like My_Application_Resource_Xxx (you inform that system that My_Application_Resource_ is a namespace prefix for plugin resources using pluginPaths.My_Application_Resource = /path/to/dir/containing/plugin in application/configs/application.ini)
You cant use this method name in your bootstrap class '_initView' because there is corresponding Zend_Application_Resource_View, just rename your bootstrap method name

Action helper inclusion error + getActionController is null

I would like to write a few action helper but it seems that I am not doing it well.
First of all, in my Bootstrap file, I am doing the following:
Zend_Controller_Action_HelperBroker::addPath(APPLICATION_PATH.'/controllers/helpers/My/Helper/', 'My_Helper');
In this directory, I have the following tree:
My/Helper/
- Fileupload/
- Abstract.php
- Xhr.php
- Fileupload.php
In my Controller, I successfully call the fileupload helper (in the class My_Helper_Fileupload):
$res = $this->_helper->fileupload($directory);
But in the constructor of this helper, I am trying get another helper (in the class My_Helper_Fileupload_Xhr) iwth:
Zend_Controller_Action_HelperBroker::getStaticHelper('Fileupload_Xhr');
which lead me to the error
Action Helper by name FileuploadXhr not found
What am I doing wrong? I tried a lot of stuff but I can't figure out what's wrong...
Moreover, after a few test, it seems that in my constructor of the fileupload helper, I am getting NULL when I call the getActionController method. Isn't it supposed to be set automatically?
The helper broker will remove underscores as part of its process of "normalizing" the helper name you give it. It only deals with CamelCased helper names, so your Fileupload_Xhr gets converted to FileuploadXhr.
The Zend_Loader that ends up being responsible for finding and loading the right PHP file uses underscores to determine when it should add a directory separator.
If you combine these two things, the practical upshot is that you can't have a nested folder structure under any path for the action helper broker. All the helper classes you want to be able to load for any path added with addPath must reside directly under the given path, with no intervening subfolders.
The simple solution is to move your helper from My/Helper/Fileupload/Xhr.php to My/Helper/FileuploadXhr.php.
Assuming you are using My as your appnamespace - which is what I usually do; Application is too long to type - then this is one way to do it.
In Bootstrap:
Zend_Controller_Action_HelperBroker::addPath(APPLICATION_PATH .'/controllers/helpers', 'My_Controller_Helper');
Name your helper class as My_Controller_Helper_SomeHelper and store it in the file application/controllers/helpers/SomeHelper.php.
Invoke in a controller using:
$this->_helper->someHelper()
If your appnamespace is different, the just replace My_ with your appnamespace.

Categories