Loading Codeigniter library from a different folder under application folder - php

Hi I am having an issue
Say I have a folder structure in CodeIgniter
application/
controllers/
models/
views/
gmail_library/
Now I have written a controller
class invite_friends extends CI_Controller {
function __construct() {
parent::__construct();
$this->load->gmail_library('Config'); // this line is giving me error
session_start();
}
}
How can I set this thing like this?

First of all, note that CodeIgniter doesn't use overloading by __call() to implement dynamic methods. Thus there is no way to get such a gmail_library() methods to work.
The conventional method
From the User Guide:
Your library classes should be placed within your
application/libraries folder, as this is where CodeIgniter will look
for them when they are initialized.
If you're using CI Loader class to load a library or helper, you should follow CI's conventions.
application/libraries/Myclass.php
$this->load->library('myclass');
$this->myclass->my_method();
Using relative paths
1) You put your library files in sub-directories within the main libraries folder:
application/libraries/gmail/Gmail_config.php
I renamed your Config.php file name to prevent the occurrence of conflict with CI config core class.
$this->load->library('gmail/gmail_config');
2) Also you can use relative path within the Loader::library() method to load the library file from the outside of the library folder, as follows:
The path to the file is relative. So you can use ../ to go one UP level in path.
Again: I renamed your Config.php file name to prevent the occurrence of conflict with CI config core class.
$this->load->library('../gmail_library/Gmail_config');

An old question, I know, but I came across this looking for a way to use classes (libraries) from outside the application folder and I liked to keep it in 'the CI way of doing this'. I ended up extending the CI_Loaderclass:
I basically copied the _ci_load_class function and added an absolute path
<? if (!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Loader extends CI_Loader {
protected $absPath = '/home/xxxxx/[any-path-you-like]/common/';
/**
* Load class
*
* This function loads the requested class.
*
* #param string the item that is being loaded
* #param mixed any additional parameters
* #param string an optional object name
* #return void
*/
public function commonLibrary($class, $params = NULL, $object_name = NULL)
{
// Get the class name, and while we're at it trim any slashes.
// The directory path can be included as part of the class name,
// but we don't want a leading slash
$class = str_replace('.php', '', trim($class, '/'));
// Was the path included with the class name?
// We look for a slash to determine this
$subdir = '';
if (($last_slash = strrpos($class, '/')) !== FALSE)
{
// Extract the path
$subdir = substr($class, 0, $last_slash + 1);
// Get the filename from the path
$class = substr($class, $last_slash + 1);
}
// We'll test for both lowercase and capitalized versions of the file name
foreach (array(ucfirst($class), strtolower($class)) as $class)
{
$subclass = $this->absPath.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php';
// Is this a class extension request?
if (file_exists($subclass))
{
$baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php';
if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
// Safety: Was the class already loaded by a previous call?
if (in_array($subclass, $this->_ci_loaded_files))
{
// Before we deem this to be a duplicate request, let's see
// if a custom object name is being supplied. If so, we'll
// return a new instance of the object
if ( ! is_null($object_name))
{
$CI =& get_instance();
if ( ! isset($CI->$object_name))
{
return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($baseclass);
include_once($subclass);
$this->_ci_loaded_files[] = $subclass;
return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
// Lets search for the requested library file and load it.
$is_duplicate = FALSE;
foreach ($this->_ci_library_paths as $path)
{
$filepath = $this->absPath.'libraries/'.$subdir.$class.'.php';
// Does the file exist? No? Bummer...
if ( ! file_exists($filepath))
{
continue;
}
// Safety: Was the class already loaded by a previous call?
if (in_array($filepath, $this->_ci_loaded_files))
{
// Before we deem this to be a duplicate request, let's see
// if a custom object name is being supplied. If so, we'll
// return a new instance of the object
if ( ! is_null($object_name))
{
$CI =& get_instance();
if ( ! isset($CI->$object_name))
{
return $this->_ci_init_class($class, '', $params, $object_name);
}
}
$is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
}
include_once($filepath);
$this->_ci_loaded_files[] = $filepath;
return $this->_ci_init_class($class, '', $params, $object_name);
}
} // END FOREACH
// One last attempt. Maybe the library is in a subdirectory, but it wasn't specified?
if ($subdir == '')
{
$path = strtolower($class).'/'.$class;
return $this->_ci_load_class($path, $params);
}
// If we got this far we were unable to find the requested class.
// We do not issue errors if the load call failed due to a duplicate request
if ($is_duplicate == FALSE)
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
}
}
Put the file MY_Loader.php in the application/core folder and load your libs with:
$this->load->commonLibrary('optional_subfolders/classname', 'classname');
$this->classname->awesome_method();

Related

Why Codeignitor does not accept autoload Controller classes when validating routes?

Why does Codeignitor not accept Controller in composer autoload when validating routes?
It's checking by: class_exists($class, FALSE) where the second parameter disables checking in autoload.
https://github.com/bcit-ci/CodeIgniter
$e404 = FALSE;
$class = ucfirst($RTR->class);
$method = $RTR->method;
if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
{
$e404 = TRUE;
}
else
{
require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php');
if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
{
$e404 = TRUE;
}
elseif (method_exists($class, '_remap'))
{
$params = array($method, array_slice($URI->rsegments, 2));
$method = '_remap';
}
elseif ( ! method_exists($class, $method))
{
$e404 = TRUE;
}
/**
* DO NOT CHANGE THIS, NOTHING ELSE WORKS!
*
* - method_exists() returns true for non-public methods, which passes the previous elseif
* - is_callable() returns false for PHP 4-style constructors, even if there's a __construct()
* - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited
* - People will only complain if this doesn't work, even though it is documented that it shouldn't.
*
* ReflectionMethod::isConstructor() is the ONLY reliable check,
* knowing which method will be executed as a constructor.
*/
elseif ( ! is_callable(array($class, $method)))
{
$reflection = new ReflectionMethod($class, $method);
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
$e404 = TRUE;
}
}
}
Looking over the git history, the change was introduced in 49e68de96b420a444c826995746a5f09470e76d9, with the commit message being:
Disable autoloader call from class_exists() occurences to improve performance
Note: The Driver libary tests seem to depend on that, so one occurence in CI_Loader is left until we resolve that.
So the nominal reason is performance.
If you want to ensure that the controller classes will be loaded on each request, you can add the files explicitly to the Composer autoload.files attribute, like so:
composer.json
{
"autoload": {
"files": [
"src/Foo.php"
]
},
"name": "test/64166739"
}
src/Foo.php
<?php
class Foo {}
test.php
<?php
$loader = require('./vendor/autoload.php');
var_dump(class_exists('Foo', false));
When run (via php test.php for example), we get the following output:
bool(true)
Additional
Looking over the code around that call to class_exists, it would appear that the controller files should follow a convention such that, for example with the built in Welcome controller and the default settings, the file that defines it should exist at:
application/controllers/Welcome.php
and so after require_onceing that file, the call to class_exists is a reasonably simple sanity check to ensure that the file did in fact define that class. So, based on this assumption about how controllers are added to the CodeIgniter application (ie all in the application/controllers directory and named the same as the class that they define), it's reasonable to bypass the autoloader when performing that check.
If you wanted to ensure the controllers are loaded when needed, the CodeIgniter way, they should be added to the application as listed above.

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.

Third_party Views Path(s) Check in CodeIgniter 3

I'm trying to check if a view file really exists according to the third_party paths loaded
Normally, I'd check if a view exists with is_file(APPPATH.'/views/folder/'.$view)
I can retrieve every loaded third_party paths with get_package_paths (thanks to the comment of Tpojka) and then check in their folder views if the file exists,
but I was hoping for a 'direct' check, as if the ->view function would return false instead of redirecting to an error page
$html = $this->load->view($tpl,'',TRUE) ? $this->load->view($tpl,'',TRUE) : $another_template;
Though I realize there might be no other solutions that adding this manual check with a loop through the loaded paths and hide it in a CI_Load Class extension (application/core/MY_Loader) to give the apparance of a direct check in the controller:
EDIT: This is a bad idea, cause view() may return false to CI function that might not be designed for
class MY_Loader extends CI_Lodaer{
public function __construct() {
parent::__construct();
}
public function view($view, $vars = array(), $return = FALSE)
{
foreach( $this->get_package_paths( TRUE ) as $path )
{
// this will only retrieve html from the first file found
if( is_file( $path."/views/".$view ) ) return parent::view($view, $vars, $return);
}
// if no match
return false;
}
}
What I find annoying is that load->view already makes a check through the paths, so this solution will add a second check and increase server consumption..
Well in the end I choose this lukewarm solution :
Instead of extending the function view() for it to return false (and having to deal with it through CI then after !) , I just made a function is_view() in the application/core/MY_Loader.php
I'm not sure MY_Loader is the correct place to place such a function, but so far it did the trick for me ...
(thx Tpojka for the indication)
in application/core/MY_Loader.php
/**
* is_view
*
* Check if a view exists or not through the loaded paths
*
* #param string $view The relative path of the file
*
* #return string|bool string containing the path if file exists
* false if file is not found
*/
public function is_view($view)
{
// ! BEWARE $path contains a beginning trailing slash !
foreach( $this->get_package_paths( TRUE ) as $path )
{
// set path, check if extension 'php'
// (would be better using the constant/var defined for file extension of course)
$path_file = ( strpos($view,'.php') === false ) ? $path."views/".$view.'.php' : $path."views/".$view ;
// this will return the path at first match found
if( is_file( $path_file ) ) return $path."views/";
}
// if no match
return false;
}
and in application/controllers/Welcome.php
$view = "frames/my_html.php";
/*
* the view file should be in
* application/third_party/myapp/views/frames/my_html.php
*
* so far, if the file does not exists, and we try
* $this->load->view($view) will redirect to an error page
*/
// check if view exists and retrieve path
if($possible_path = $this->load->is_view($view))
{
//set the data array
$data = array("view_path"=>$possible_path);
// load the view knowing it exists
$this->load->view($view,$data)
}
else echo "No Template for this frame in any Paths !";
and of course in the view
<h1>My Frame</h1>
<p>
The path of this file is <=?$view_path?>
</p>

Cannot redeclare class: how to autoload a class if exists already in a folder?

How can I check if a class exists already in a folder then do not load this class again from another folder?
I have this folder structure for instance,
index.php
code/
local/
And I have these two identical classes in code/ and local/
from local/
class Article
{
public function getArticle()
{
echo 'class from local';
}
}
from core,
class Article
{
public function getArticle()
{
echo 'class from core';
}
}
So I need a script that can detects the class of Article in local/ - if it exits already in that folder than don't load the class again from core/ folder. Is it possible?
This is my autoload function in index.php for loading classes,
define ('WEBSITE_DOCROOT', str_replace('\\', '/', dirname(__FILE__)).'/');
function autoloadMultipleDirectory($class_name)
{
// List all the class directories in the array.
$main_directories = array(
'core/',
'local/'
);
// Set other vars and arrays.
$sub_directories = array();
// When you use namespace in a class, you get something like this when you auto load that class \foo\tidy.
// So use explode to split the string and then get the last item in the exloded array.
$parts = explode('\\', $class_name);
// Set the class file name.
$file_name = end($parts).'.php';
// List any sub dirs in the main dirs above and store them in an array.
foreach($main_directories as $path_directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$path_directory), // Must use absolute path to get the files when ajax is used.
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject)
{
if ($fileObject->isDir())
{
// Replace any backslash to '/'.
$pathnameReplace = str_replace('\\', '/', $fileObject->getPathname());
//print_r($pathnameReplace);
// Explode the folder path.
$array = explode("/",$pathnameReplace);
// Get the actual folder.
$folder = end($array);
//print_r($folder);
// Stop proccessing if the folder is a dot or double dots.
if($folder === '.' || $folder === '..') {continue;}
//var_dump($fileObject->getPathname());
// Must trim off the WEBSITE_DOCROOT.
$sub_directories[] = preg_replace('~.*?(?=core|local)~i', '', str_replace('\\', '/', $fileObject->getPathname())) .'/';
}
}
}
// Mearge the main dirs with any sub dirs in them.
$merged_directories = array_merge($main_directories,$sub_directories);
// Loop the merge array and include the classes in them.
foreach($merged_directories as $path_directory)
{
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name))
{
// There is no need to use include/require_once. Autoload is a fallback when the system can't find the class you are instantiating.
// If you've already included it once via an autoload then the system knows about it and won't run your autoload method again anyway.
// So, just use the regular include/require - they are faster.
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
}
}
// Register all the classes.
spl_autoload_register('autoloadMultipleDirectory');
$article = new Article();
echo $article->getArticle();
of course I get this error,
Fatal error: Cannot redeclare class Article in C:\wamp\...\local\Article.php on line 3
class_exists seems to be the answer I should look into, but how can I use it with the function above, especially with spl_autoload_register. Or if you have any better ideas?
Okay, I misunderstood your question. This should do the trick.
<?php
function __autoload($class_name) {
static $core = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "core";
static $local = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "local";
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR):
$file_local = "{$local}{$file_name}.php";
require is_file($file_local) ? $file_local : "{$core}{$file_name}.php";
}
This is easily solved by using namespaces.
Your core file goes to /Core/Article.php:
namespace Core;
class Article {}
Your local file goes to /Local/Article.php:
namespace Local;
class Article {}
And then use a very simple autoloader, e.g.:
function __autoload($class_name) {
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR);
require "/var/www{$file_name}.php";
}
PHP loads your classes on demand, there's no need to load the files up front!
If you want to use an article simply do:
<?php
$coreArticle = new \Core\Article();
$localArticle = new \Local\Article();

New server ignoring capitalization in PHP app

I have worked on a codeigniter 2.1.3 application which was developed on windows running wamp 2.2 (php 5.4.3). I recently uploaded the application to a ubuntu 12.04 server running apache 2.2.22 and php 5.4.6.
My model classes are named like billView.php, categoryModel.php etc. Note the capital letters. The name of the classes inside the php files is also the same. And the name i give when calling the models from controller classes is also the same.
But when I run my app on Ubuntu, I get this error
Unable to locate the model you have specified: billview
The error is thrown from this line:
$this->load->model('billView');
(i.e. php is ignoring the capital letter)
When I rename the model file (only the model filename, class name stays intact) then the error disappears.
How to solve this problem without manually renaming all my files?
From the documentation:
Where Model_name is the name of your class. Class names must have the
first letter capitalized with the rest of the name lowercase. Make
sure your class extends the base Model class.
It's better to follow the naming convention than to work around it.
Hope this helps.
The problem that you are facing is that Windows filesystem (NTFS) is case insensitive, so in windows, billview.php and billView.php are the same file.
On Linux, as you might be guessing now, the typical filesystems (ext2/3/4, xfs, reiserfs...) are case sensitive, and for that reason, billview.php and billView.php are (or may be) different files. In your case, one exists and the other does not.
Inside CodeIgniter autoloader function/method/class/whatever, it is trying to require the file that has the class you are instantiating, so if you tell it that you need the model billview, it will try to require path/to/model/billview.php;, and this file does not exist, so the model doesn't get loaded and then your application fails.
Of course it is recommended to follow a naming convention if there is one as Amal Murali suggests, but it is not the issue here. If all your classes, files, and instances in code had the same capitalization (whether it may be all_lowercase, ALL_UPPERCASE, camelCase or sTuPiD_CaSe) everything would have worked.
So, please refer to your files/class names with the same capitalization as you have created them, and the capitalization in a class name should follow that of the file name it is stored in.
You will have the same problems if your html code refers to files (images, css, js files) in different capitalization for the same reason. The webserver will be looking for image.jpg but it will not exist (the existing file would be Image.JPG for example).
In a related note, variables in php are case sensitive, but functions and classes aren't. despite that, call them always with the right capitalization to avoid problems.
I personally have found this convention in CodeIgniter to make no sense. I wouldn't recommend hacking the CodeIgniter core but you can easily extend the CI_Loader class. Here is mine from CI version 2.2.0.
<?php
class MY_Loader extends CI_Loader
{
/**
* Model Loader
*
* This function lets users load and instantiate models.
*
* #param string the name of the class
* #param string name for the model
* #param bool database connection
* #return void
*/
public function model($model, $name = '', $db_conn = FALSE)
{
if (is_array($model))
{
foreach ($model as $babe)
{
$this->model($babe);
}
return;
}
if ($model == '')
{
return;
}
$path = '';
// Is the model in a sub-folder? If so, parse out the filename and path.
if (($last_slash = strrpos($model, '/')) !== FALSE)
{
// The path is in front of the last slash
$path = substr($model, 0, $last_slash + 1);
// And the model name behind it
$model = substr($model, $last_slash + 1);
}
if ($name == '')
{
$name = $model;
}
if (in_array($name, $this->_ci_models, TRUE))
{
return;
}
$CI =& get_instance();
if (isset($CI->$name))
{
show_error('The model name you are loading is the name of a resource that is already being used: '.$name);
}
//$model = strtolower($model);
foreach ($this->_ci_model_paths as $mod_path)
{
if ( ! file_exists($mod_path.'models/'.$path.$model.'.php'))
{
continue;
}
if ($db_conn !== FALSE AND ! class_exists('CI_DB'))
{
if ($db_conn === TRUE)
{
$db_conn = '';
}
$CI->load->database($db_conn, FALSE, TRUE);
}
if ( ! class_exists('CI_Model'))
{
load_class('Model', 'core');
}
require_once($mod_path.'models/'.$path.$model.'.php');
//$model = ucfirst($model);
$CI->$name = new $model();
$this->_ci_models[] = $name;
return;
}
// couldn't find the model
show_error('Unable to locate the model you have specified: '.$model);
}
}
?>
All I did was copy the the CI_Loader::model method then comment out these two lines
//$model = strtolower($model);
//$model = ucfirst($model);
All you have to do is put the above class in your application/core/ folder and it should work.

Categories