where to save custom autoloaders in zend? - php

I am trying to use phpThumb in my application by making a custom autoloader.
class My_Loader_Autoloader_PhpThumb implements Zend_Loader_Autoloader_Interface {
static protected $php_thumb_classes = array(
'PhpThumb' => 'PhpThumb.inc.php',
'ThumbBase' => 'ThumbBase.inc.php',
'PhpThumbFactory' => 'ThumbLib.inc.php',
'GdThumb' => 'GdThumb.inc.php',
'GdReflectionLib' => 'thumb_plugins/gd_reflection.inc.php',
);
/**
* Autoload a class
*
* #param string $class
* #return mixed
* False [if unable to load $class]
* get_class($class) [if $class is successfully loaded]
*/
public function autoload($class) {
$file = APPLICATION_PATH . '/../library/PhpThumb/' . self::$php_thumb_classes[$class];
if (is_file($file)) {
require_once($file);
return $class;
}
return false;
}
}
i saved this file as PhpThumb.php in the loaders/Autoloader folder. Then added this line to the bootstrap file:
Zend_Loader_Autoloader::getInstance()->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
But it produces an error saying the class was not found. I am guessing that the 'CustomLoader_PhpThumb.php' needs to be saved somewhere else. Any idea guys ?
Update1:
Bootstrap.php file contents
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initAutoload()
{
$autoLoader=Zend_Loader_Autoloader::getInstance();
$resourceLoader=new Zend_Loader_Autoloader_Resource(array(
'basePath'=>APPLICATION_PATH,
'namespace'=>'',
'resourceTypes'=>array(
'form'=>array(
'path'=>'forms/',
'namespace'=>'Form_'
),
'models'=>array(
'path'=>'models/',
'namespace'=>'Model_'
),
)
));
//return $autoLoader;
$resourceLoader->addResourceType('loader', 'loaders/', 'My_Loader_');
$autoLoader->pushAutoloader($resourceLoader);
$autoLoader->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
}
}
?>

I'm also using PhpThumb and the same autoloader. In my case however it is called My_Loader_Autoloader_PhpThumb and is located in APPLICATION_PATH . '/loaders/Autoloader/PhpThumb.php.
In my Bootstrap.php, first I load the loaders path to the Zend_Loader_Autoloader and then I push the My_Loader_Autoloader_PhpThumb autoloader as follows:
$autoLoader = Zend_Loader_Autoloader::getInstance();
$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH,
'namespace' => '',
));
$resourceLoader->addResourceType('loader', 'loaders/', 'My_Loader_');
$autoLoader->pushAutoloader($resourceLoader);
$autoLoader->pushAutoloader(new My_Loader_Autoloader_PhpThumb());
Hope this will help.

PhpThumb.php should be saved to library/CustomLoader folder, and you should setup autoloader for this class also, for example in application.ini:
autoloaderNamespaces[] = "CustomLoader_"
or in Bootstrap.php:
$autoloader->registerNamespace('CustomLoader_');
I couldn't try, I hope it works!

I did it in another way, long more easy, without any autoloader, just using "My".
1.- Create a phpThumbFolder with all the files of the library into project/library/My/PhpThumb
2.- Create a file named PhpThumbFactory.php into that folder with the following content:
require_once('ThumbLib.inc.php');
class My_PhpThumb_PhpThumbFactory
{
public static function create ($filename = null, $options = array(), $isDataStream = false)
{
return PhpThumbFactory::create($filename, $options, $isDataStream);
}
}
3.- Enjoy:
public function editimageAction()
{
//rutaImg is the physical path to my images defined in application.ini
$rutaImg = Zend_Registry::get('config')->rutaImg. "test.jpg";
//Call to our "gateway" class
$thumb = My_PhpThumb_PhpThumbFactory::create($rutaImg);
//Resize
$thumb->adaptiveResize(20, 20);
//Show
$thumb->show();
$this->_helper->viewRenderer->setNoRender();
$this->_helper->layout->disableLayout();
}
I hope it can help anyone ;)
P.D. The "gateway" class is to avoid modifying any code of the library, so any possible update is easy.

Related

Cake PHP 2 App::build() load CSS from custom module

I'm working on a Cake PHP 2 project that has a custom module system allowing for a folder within app called modules, and then each module can have Views, Controllers, Models etc... I've extended it to include a webroot directory with a css directory inside and have added these directories to my App::build() array.
I've got some css in my module, and a layout which is working, but trying to load my module's css doesn't load it and instead is trying to pull the css from my main app's webroot, where of course it doesn't exist.
What am I missing?
My paths are:
app/modules/QueueManagerModule/webroot/css/queue.css
app/modules/QueueManagerModule/View/Layouts/QueueManagerLayout.ctp
I'm doing the following inside of my layout to echo CSS:
echo $this->Html->css('queue');
This though generates the path of: mydomain.com/css/queue.css.
My module set up for loading everything looks like:
<?php
App::uses('BaseModule', 'Modules');
App::uses('CakeEventManager', 'Event');
/**
* Helper class to load modules of a specific format from /app/modules directory,
* and create instances that can connect to system events, modify system behaviours etc.
*
* Usage:
*
* $_modules = new Modules();
$mods_arr = $_modules->initModules(ROOT.'/app/modules');
*
*
*/
class Modules
{
public function initModules($modules_base_dir)
{
$modules = array();
//loop over all directories in /app/modules/
foreach (new DirectoryIterator($modules_base_dir) as $dir)
{
if($dir->isDot()) continue;
if($dir->getFilename()=='.svn') continue;
if($dir->isFile()) {
continue;
}
//compile a list of all modules, and load each Module class
$classname = $dir->getFilename();
App::import('modules/'.$classname, $classname);
$module = new $classname();
array_push($modules, $module);
//enumerate all events from BaseModule so we know what we need to handle
$base_events_array = array();
$methods = get_class_methods('BaseModule');
foreach($methods as $method)
{
//strip out any method that starts with "handle_"
if(substr($method, 0, 7)=='handle_')
{
$base_events_array[] = substr($method, 7);
}
}
//IF this module is enabled
if($module->_enabled)
{
//register any EXISTING event handlers for this module
foreach($base_events_array as $event_name)
{
if(method_exists($module, 'handle_'.$event_name))
{
CakeEventManager::instance()->attach(array($module, 'handle_'.$event_name), $event_name);
}
}
//connect up any additional controllers,views, models, bootstraps from this module
App::build(array(
'Config' => array($modules_base_dir.'/'.$classname.'/Config/'),
'Console/Command' => array($modules_base_dir.'/'.$classname.'/Console/Command/'),
'Console/Command/Task' => array($modules_base_dir.'/'.$classname.'/Console/Command/Task/'),
'Lib/Event' => array($modules_base_dir.'/'.$classname.'/Lib/Event/'),
'Controller' => array($modules_base_dir.'/'.$classname.'/Controller/'),
'View' => array($modules_base_dir.'/'.$classname.'/View/'),
'View/Elements' => array($modules_base_dir.'/'.$classname.'/View/Elements'),
'Model' => array($modules_base_dir.'/'.$classname.'/Model/'),
'Vendor' => array($modules_base_dir.'/'.$classname.'/Vendor/'),
'webroot' => array($modules_base_dir.'/'.$classname.'/webroot/'),
'webroot/css' => array($modules_base_dir.'/'.$classname.'/webroot/css/'),
'webroot/js' => array($modules_base_dir.'/'.$classname.'/webroot/js/')
));
if(file_exists($modules_base_dir.'/'.$classname.'/Config/events.php'))
{
require_once $modules_base_dir.'/'.$classname.'/Config/events.php';
}
if(file_exists($modules_base_dir.'/'.$classname.'/bootstrap.php'))
{
include_once $modules_base_dir.'/'.$classname.'/bootstrap.php';
}
}
}
//die(var_dump(App::path('Controller')));
return $modules;
}
}

list folders where Codeigniter searches for classes

Codeigniter does not find trivial classes:
Unable to load the requested class: Bcrypt
But the same goes for custom made classes defined in files in application/libraries/. I am used that django lists the folders where it searched for a file, but did not find one. Obviously CI must also iterate over some list of folders or files, but is not as polite to display them along with the error.
It seems as if CI has a naming convention to deduce the (set of) filename(s) where it would expect a class to be. How can I programmatically error_log the list of folders or filenames that Codeigniter or PHP tried to track down this class?
EDIT: The lines of code that produce such a loading-error are:
$autoload['libraries'] = array('database','session','mi_file_fetcher');
in application/config/autoload.php and
$this->load->library("bcrypt");
in application/models/User.php
As stated in the comments, I was not asking for a fix, I was asking for a list.
I managed to do so by updating system/core/Loader.php
protected function _ci_load_library_files_tried($class, $subdir, $params, $object_name)
{
$files_tried = array(BASEPATH . 'libraries/' . $subdir . $class . '.php');
foreach ($this->_ci_library_paths as $path) {
if ($path === BASEPATH) {
continue;
}
array_push($files_tried, $path . 'libraries/' . $subdir . $class . '.php');
}
return $files_tried;
}
protected function _ci_load_library($class, $params = NULL, $object_name = NULL)
{
// ...
// If we got this far we were unable to find the requested class.
$files_tried = $this->_ci_load_library_files_tried($class, $subdir, $params, $object_name);
log_message('error', 'Unable to load the requested class: '.$class .
", tried these files:\n" . join("\n", $files_tried));
show_error('Unable to load the requested class: '.$class .
', tried these files:<ul><li>' . join('</li><li>', $files_tried) . '</li></ul>');
}
Would be great if CI actually provided decent debugging information.
The CodeIgniter (CI) documentation does tell you the default locations of libraries, models, helpers, views and many other framework objects. There isn't a section that explicitly lists the folders though. The Loader Class documentation does tell you, but you have to dig for it a bit.
The subtopics on the General Topics section of the docs clearly state the default locations for the various classes the frameworks uses and where to put custom classes.
In most cases following the prescribed file structure and using the loader class, e.g. (Bcrypt.php is in /application/libraries/)
$this->load->library('bcrypt');
works perfectly.
There are cases (none of which seem to be involved in your problem) where CI needs help. Rather than hack or extend CI_Loader an autoloader is useful in these cases.
There are lots of ways to add an autoloader but my preference is to use CI's Hooks feature. Here's how.
In config.php set 'enable_hooks' to TRUE
$config['enable_hooks'] = TRUE;
These lines go in /application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
The following is the contents of /application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
/**
* Allows classes that do not start with CI_ and that are
* stored in these subdirectories of `APPPATH`
* (default APPPATH = "application/" and is defined in "index.php")
* libraries,
* models,
* core
* controllers
* to be instantiated when needed.
* #param string $class Class name to check for
* #return void
*/
function my_autoloader($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;
}
elseif(file_exists($file = APPPATH.'controllers/'.$class.'.php'))
{
require_once $file;
}
}
}
The function log_message($level, $message) could be used in the above if you wanted.
If you are using some other creative folder structure you will have to modify the above to accommodate that layout.

Twig 1.x - Configuring cache to /tmp don't work

If I configure Twig's cache to myapp/storage/cache and manually set the correct permissions it works, but configuring it to sys_get_temp_dir() (which returns /tmp) doesn't seem to work. File structure in /tmp remains the same, altough no error is triggered.
My block of code is like this:
// [...]
$app->register(new \Silex\Provider\TwigServiceProvider(), array(
'twig.path' => __DIR__ . '/templates',
'twig.options' => array(
'cache' => sys_get_temp_dir(), // If changed to myapp/storage/cache, it works.
),
));
I don't know if this can help you, but it is possible to override the default writeCacheFile method of the Twig_Environment. By doing so you can create the temporary folder yourself and apply the wanted permissions, so your user don't have to do this themself.
Custom Twig_Env
class Environment extends \Twig_Environment {
protected function writeCacheFile($file, $content){
createDirectoryTree(dirname($file));
parent::writeCacheFile($file, $content);
chmod($file,0664);
chgrp($file, 'psacln');
chown($file, 'www-data');
}
}
Functions.php
function createDirectoryTree($folder) {
if (is_dir($folder)) return;
$folder = str_replace('/', DIRECTORY_SEPARATOR, $folder);
$branches = explode(DIRECTORY_SEPARATOR, $folder);
$tree = '';
$old_mask = umask(0);
while(!empty($branches)) {
$tree .= array_shift($branches).DIRECTORY_SEPARATOR;
if (!#file_exists($tree)) {
if (#mkdir($tree, 0774)){
chown($tree, 'www-data');
chgrp($tree, 'psacln');
}
}
}
umask($old_mask);
}

Phalcon backup view path

Is there any way to pass through a secondary path to the views dir in phalcon?
in zend framework I think the syntax is
$this->view->addScriptPath('/backup/path');
$this->view->addScriptPath('/preferred/path');
so if there is a file in the preferred path it will use it, if not it will fallback through the chain.
I use this, for example, for mobile versions when most of the pages are the same, but some have to be significantly different and I don't want to have to duplicate all the views just for 2 or 3 variants
In phalcon I have tried sending an array to the view, but that just results in neither working
$di->set('view', function() use ($config) {
$view = new \Phalcon\Mvc\View();
$view->setViewsDir( array('/preferred/path/', '/backup/path/') );
return $view;
});
I've got this working by extending the Phalcon\Mvc\View\Engine\Volt
In the render($template_path, $params, $must_clean = null) method I set the alternative path, check if file is available and if so I switch the $template_path given with the alternative path. Then it's just a case of calling:
return parent::render($template_path, $params, $must_clean);
where $template_path contains the new (alternative) path.
If your alternative path might change on a per project basis and you need to set it in bootstrap, then rather than doing it when getting a "view" from di you would do it when getting volt.
Just remember that all views are rendered with that method so you will have to account for layout and partial views as well - depending on your implementation.
Example: (this has not been tested, it's based on a similar set up I have in my own code)
<?php
class Volt extends Phalcon\Mvc\View\Engine\Volt
{
private $skin_path;
public function render($template_path, $params, $must_clean = null)
{
$skin_template = str_replace(
$this->di->getView()->getViewsDir(),
$this->getSkinPath(),
$template_path
);
if (is_readable($skin_template)) {
$template_path = $skin_template;
}
return parent::render($template_path, $params, $must_clean);
}
public function setSkinPath($data)
{
$this->skin_path = $data;
}
public function getSkinPath()
{
return $this->skin_path;
}
}
In your bootstrap:
$di->setShared('volt', function($view, $di) {
$volt = new Volt($view, $di);
$volt->setSkinPath('my/alternative/dir/');
return $volt;
});
Many thanks to nickolasgregory#github who pointed me in the right direction.
Method proposed by #strayobject helps me also, but I've found that using extend or other statements inside volt templates dosn't work.
Here's refined solution that works with extend and include:
use Phalcon\Mvc\View\Engine\Volt;
class VoltExtension extends Volt
{
// Override default Volt getCompiler method
public function getCompiler()
{
if (!$this->_compiler) {
$this->_compiler = new VoltCompilerExtension($this->getView());
$this->_compiler->setOptions($this->getOptions());
$this->_compiler->setDI($this->getDI());
}
return $this->_compiler;
}
}
And
use Phalcon\Mvc\View\Engine\Volt;
class VoltCompilerExtension extends Volt\Compiler
{
public function compileFile($path, $compiledPath, $extendsMode = null)
{
$skinPath = $this->getOption('skinPath');
if ($skinPath) {
$skinTemplate = str_replace(
$this->getDI()->getView()->getViewsDir(),
$skinPath,
$path
);
if (is_readable($skinTemplate)) {
$path = $skinTemplate;
}
}
return parent::compileFile($path, $compiledPath, $extendsMode);
}
}
Usage:
$volt = new VoltExtension($view, $di);
$volt->setOptions(
array(
'compiledPath' => $config->application->cacheDir,
'compiledSeparator' => '_',
'compileAlways' => false,
'skinPath' => $config->application->skinPath
)
);
Please take a look at this phalcon framework update. It provides support for multiple view packages per website (you can have multiple websites). Users of the magento framework will find it easy to use:
https://github.com/alanbarber111/cloud-phalcon-skeleton

Why does class get redeclared multiple times?

Ok here is a method I use for initializing models in my controller actions:
protected $_tables = array();
protected function _getTable($table)
{
if (false === array_key_exists($table, $this->_tables)) {
include APPLICATION_PATH . '/modules/'
. $this->_request->getModuleName() . '/models/' . $table . '.php';
$this->_tables[$table] = new $table();
echo 'test ';
}
return $this->_tables[$table];
}
Then when I call the _getTable() method two times (for example once in init() method and once in the controller action) it prints:
test test test test test test
On top of the page. Shouldn't it just return the object from the _tables array() because of the array_key_exists() check? In other words shouldn't the part inside the array_key_exists() function get executed only once when the method is called multiple times?
UPDATE:
So the problem is this - for some reason the layout gets printed twice (so it's layout printed and inside the layout where there is layout()->content; ?> it prints the layout again). I have no idea why it does this as it worked well on the previous server and also on localhost.
In the snippet you show:
protected $this->_tables = array();
This is not valid syntax, it should be:
protected $_tables = array();
Also, why not just use include_once and let PHP handle this for you? Alternatively, you could use the Zend_Loader. Don't reinvent the wheel.
What you are really looking for is the loading of module based resources. Instead of re-inventing the wheel, why not just use the (module) resource autoloaders of ZF? See the documentation at:
http://framework.zend.com/manual/en/zend.loader.autoloader-resource.html
When you use Zend_Application (I'm assuming you don't), you get these automatically. If you don't you could do something like
$loaders = array();
$frontController = Zend_Controller_Front::getInstance();
foreach($frontController->getControllerDirectory() as $module => $directory) {
$resourceLoader = new Zend_Application_Module_Autoloader(array(
'namespace' => ucfirst($module) . '_',
'basePath' => dirname($directory),
));
$resourceLoader->addResourceTypes(array(
'table' => array(
'path' => 'models/',
'namespace' => 'Table'
));
$loaders[$module] = $resourceLoader;
}
//build array of loaders
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setAutoloaders($loaders);
//set them in the autoloader
This approach is a bit naive, but it should give you nice autoloading.

Categories