Override of Zend_View_Helper not being picked up in Zend_Layout - php

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.

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.

Where can I put my global code in Kohana 3.3?

Say I want a constant, a function or a class to be available in all my models, views and controllers code (within a particular web site (application)). Where can I put them? I don't mind importing them explicitly in every file I need them in but I don't know how (simple require_once does not seem to work).
You can put them in the vendor folder (in application/vendor or modules/MOD/vendor). Then you can load it like this:
require Kohana::find_file('vendor', 'folder/file','ext');
You can read up more on this in the user guide
Now, it should be stated you should in general not use functions or globals.
I declare Constants in Bootstrap.php and create my own Helpers for general functions under application/classes/Helpers.
If you need to integrate a third party library into Kohana or want to make code available to other Kohana users consider creating a module instead.
You can define all your constants in new php file and place it in the application/classes directory. In your Template controller or in the main controller like Welcome or Website, just place the code in the __constructor()
require_once Kohana::find_file( 'classes', 'filename' );
I suggest you to put your constant variables in the bootstrap.php file because this is loaded by the framework before every request.
You can simply put your classes in the root of the classes directory, the framework will find.
This worked well for me...
In application/config/constants.php:
define('SOME_COOL_CONSTANT', 'foo');
return array();
In index.php:
Kohana::$config->load('constants');
SOME_COOL_CONSTANT should then be available globally.

Zend framework 1.12 front controller configuration

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.

How to register Yii's autoloader in an external application?

I want to try Yii, but I don't want use it as my main framework. In other words, I want to use my own framework while also using some of Yii's features. I figured that in order to be able to instantiate Yii's classes from my application, I'd just need to register Yii's autoloader from my application, probably in a way similar to this:
spl_autoload_register
(
function ($classname)
{
YiiBase::autoload($className);
}
);
Of course, I'm gonna need to require or include the YiiBase class, so before I call the previous function, I do this:
$yiiBase = $_SERVER['DOCUMENT_ROOT'].'/yii/framework/YiiBase.php';
require_once($yiiBase);
But I get a "Cannot redeclare class YiiBase" error. What am I missing?
1) Do not include YiiBase.php direcly, include yii.php. Because yii.php contains a class Yii which is used in all over framework code (even in YiiBase methods).
$yii = $_SERVER['DOCUMENT_ROOT'].'/yii/framework/yii.php';
require_once($yii);
( and YiiBase.php is included in yii.php by default)
2) register your autoload handler in this way.
(Yii has built-in functionality to add custom autoload handlers ).
$my_autoload = function($class) { ... };
// OR
// $my_autoload = array('MyClass', 'my_autoload')
YiiBase::registerAutoloader($my_autoload, true);
The second parameter true tells whether to append/prepend the new autoloader after/before the default Yii autoloader
if the YiiBase.php included, then Yii's default autoloader will also gets included. No need to call YiiBase::autoload() explicitly in you code. Ref: check the last line in YiiBase.php file
You can take a look at some approaches of people integrating Yii with wordpress, you may not need to do ->run() the application, unless you need the controllers/routing, it also depends on what parts of the framework you pretend to use.
To do it for Yii2, this article explains it under the heading "Using Yii in Third-Party Systems" in the middle of the page.
Here is the relevant part to include on startup of your external application:
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
$yiiConfig = require(__DIR__ . '/../config/yii/web.php');
new yii\web\Application($yiiConfig); // Do NOT call run() here
Obviously, __DIR__ . '/../ may need to be adjusted to fit your directory layout.

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