I have a project that was developed without namespaces and with a "private framework" written by my team at the time. Said framework depends on an autoload function that includes files from within the framework and automatically finds files inside the project, which means that inside the project we have 0 includes/requires. Every file follows a specific rule and gets included by my function.
Everytime we would use third-party library, we would download the files and place them at a specific place and work on getting the files property loaded.
This week I found a new library I wanted to use, so I decided to install it via composer. Now my autoload function doesn't exist and my framework stops any execution at the begin for missing files.
How do I go about keeping my autoload as is (that includes files without namespaces from my project and my framework) and still use composer? Is it possible or I'm dead?
Edit: Adding some files to the question.
frameworkBootstrap - This file loads the framework (works just fine).
<?php
$dir = dirname(__FILE__);
// Database Package
require $dir . '/nav/database/NavDao.php';
require $dir . '/nav/database/NavDatabase.php';
require $dir . '/nav/database/NavTable.php';
// General
require $dir . '/nav/general/NavLanguage.php';
require $dir . '/nav/general/NavProject.php';
require $dir . '/nav/general/NavController.php';
// Tool
require $dir . '/nav/tool/NavValidator.php';
require $dir . '/nav/tool/NavLogger.php';
require $dir . '/nav/tool/NavListener.php';
require $dir . '/nav/tool/NavFile.php';
require $dir . '/nav/tool/NavEmail.php';
require $dir . '/nav/tool/NavException.php';
// View
require $dir . '/nav/view/NavPage.php';
require $dir . '/nav/view/NavTemplate.php';
require $dir . '/nav/view/NavView.php';
// Request
require $dir . '/nav/request/NavRequest.php';
require $dir . '/nav/request/NavAccess.php';
require $dir . '/nav/request/NavResponse.php';
require $dir . '/nav/request/NavSession.php';
// Plugin
NavProject::plugin(
array(
'NavMail' => $dir . '/nav/plugin/email/NavMail.php',
'NavXPertMailer2006' => $dir . '/nav/plugin/email/NavXPertMailer2006.php',
'NavLog' => $dir . '/nav/plugin/log/NavLog.php',
'NavImage' => $dir . '/nav/plugin/file/NavImage.php',
'NavMysql' => $dir . '/nav/plugin/dbms/NavMysql.php',
'NavOracle' => $dir . '/nav/plugin/dbms/NavOracle.php',
'NavTranslate' => $dir . '/nav/plugin/translate/NavTranslate.php'
));
require $dir . '/vendor/autoload.php';
?>
Autoload funciton - This funciton is being replaced.
function __autoload($className) {
$file = '';
// Auto Load Template
if (strpos($className, 'Template') !== false)
$file = NavProject::path() . 'class/view/template/' . $className . '.php';
// Auto Load Project Tools
else if (strpos(strtolower($className), strtolower(NavProject::name())) !== false)
$file = NavProject::path() . 'class/tool/' . $className . '.php';
// Auto Load Controllers
else if (strpos($className, 'Controller') !== false)
$file = NavProject::path() . 'class/control/' . $className . '.php';
// Auto Load Nav Plugin
else if (strpos($className, 'Nav') === 0) {
$list = NavProject::plugin();
foreach ($list as $plugin => $location)
if ($plugin == $className)
$file = $location;
// Auto Load Model
} else {
$file = NavProject::path() . 'class/model/' . $className . '.php';
}
if (is_file($file))
require $file;
}
3rd-party library
https://github.com/alexshelkov/SimpleAcl
You should go the slightly longer route and replace your own autoloading with that of Composer.
Short-term you'd be able to use the classmap feature to autoload every existing code:
"autoload": {
"classmap":
["class/view/template/","class/tool/","class/control/","class/model", "nav/"]
}
The classmap should contain every directory that you have classes in, and the path should be relative from the top level directory where you place your own composer.json.
You'd then remove your own __autoload function (there can be only one defined, and if you do, you cannot have another libraries autoloading active at the same time - the way to do autoloading even long before Composer was to use the "spl_autoload" feature), and you'd also remove every require statement in your framework bootstrap, and replace it with require "vendor/autoload.php";. After autoloading is included, you have access to every class in the classmap of your own code, and to every library you add.
You have to run composer install once to create the classmap.
If you create new classes after this, you again have to run composer dump-autoload to refresh the map. If this gets annoying to you, think about switching to using PSR-0 autoloading instead. I think it's doable with your code base already, but I'm reluctant to suggest something without knowing what your class naming conventions really are.
For example, the autoloader will assume that any class that has "Template" SOMEWHERE in it's name is located in the "class/view/template" folder. This includes TemplateHomepage as well as HomepageTemplate as well as PageTemplateHome. Your autoloader allows for a multitude of names to be loaded, but I suppose you are only using one scheme.
Try replacing __autoload with spl_autoload_register();
Why?
__autoload(); is discouraged and may be deprecated or removed in
the future from docs.
This function can collide with spl_autoload_register() which is used by composer.
spl_autoload_register('my_autoload', false, true);
Then replace __autoload to my_autoload.
Related
Here is a partial structure of my project
root
|-App
|--Controller
|--Common
|-HomeController.php
|-HeaderController.php
|-FooterController.php
|-SidebarController.php
|--Info
|-AboutController.php
|-ContactController.php
I have my controllers placed in their corresponding directories for better management.
I recently added namespaces to them.
HomeController.php = namespace app\controller\common;
HeaderController.php = namespace app\controller\common;
AboutController.php = namespace app\controller\info;
ContactController.php = namespace app\controller\info;
To load these controllers, I am checking this autoloader below
spl_autoload_register(function ($class) {
$prefix = 'app\controller\common\\';
$base_dir = __DIR__ . '/app/controller/common/'; // your classes folder
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
$home = new app\controller\common\HomeController();
$home->index();
and it works i.e. it autoload all controllers in the folder app > controller > common.
The problem is I do not understand how to load all other controllers which are in different folders (like the ones in the Info folder) and with different sub-namespace (namespace app\controller\info, namespace app\controller\client)?
The autoload has the namespace prefixed defined to $prefix = 'app\controller\common\\'; and I guess this is what I need to fix to accommodate all other controllers that there is to load them.
How do I fix this $prefix?
You had the right idea there. But you went for anonymous functions, which in itself isn't necessarily wrong it just doesn't fit the your current case problem very well. You're better of with regular functions.
You could do something like this:
spl_autoload_register('myAutoloader');
function myAutoloader($path) {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path),\FilesystemIterator::SKIP_DOTS) as $file) {
if ($file->getFileExtension() == 'php') {
require $file->getFilename() . $file->getFileExtension();
}
}
}
This function will recursively iterate thru a folder and require any php file it finds. What it won't do is take care of various dependencies, so keep that in mind.
spl_autoload_register() allows you to register multiple functions (or static methods from your own Autoload class) that PHP will put into a stack/queue and call sequentially when a "new Class" is declared.
Quote is from this question, which I strongly recommend you read.
With that said, I encourage you to use Composer. It was designed specifically for your exact problem. You can load anything with it. Not just php files.
I was just going through the code of PhileCMS and came across the following lines of code:
if (Registry::isRegistered('Phile_Settings')) {
$config = Registry::get('Phile_Settings');
if (!empty($config['base_url'])) {
return $config['base_url'];
}
}
The file can be seen HERE
How come the static method of class Registry can be used here, when the file is not included or required at all? Is there some kind of auto loading going on in the backend that can't be seen? If so, what is this new kind of auto loading mechanism that has emerged?
Read more about of classes autoloading in PHP: http://php.net/manual/en/language.oop5.autoload.php
In PhileCMS the classes autoloading is confugired in the Phile\Bootstrap::initializeAutoloader() method (copy-paste of method body from the github for convinience):
spl_autoload_extensions(".php");
// load phile core
spl_autoload_register(function ($className) {
$fileName = LIB_DIR . str_replace("\\", DIRECTORY_SEPARATOR, $className) . '.php';
if (file_exists($fileName)) {
require_once $fileName;
}
});
// load phile plugins
spl_autoload_register('\Phile\Plugin\PluginRepository::autoload');
require(LIB_DIR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php');
https://github.com/PhileCMS/Phile/blob/master/lib/Phile/Bootstrap.php#L93
I need to create a simple file overloading system like symfony does with php files and templates. I will give an example to explain what I need:
Given this folder structure:
- root_folder
- modules
-module1
-file1.php
-file2.php
-file3.php
- specific_modules
-module1
-file2.php
I would like to find a way that automatically loads a file if it is found inside the specific_modules folder (file2.php) when called, if it is not found, it should load file2.php normally from the modules directory.
I would like to do it unobstrusively for the programmer, but not sure if it's possible!!
Any help or advice is welcome, thanks in advance!
skarvin
If the files contain only objects with the same name, then you can write your own autoloader function and register it with spl_autoload_register(). Perhaps something like
function my_loader($class)
{
// look in specific_modules dir for $class
// if not there, look in modules dir for $class
}
spl_autoload_register('my_loader');
This will allow you to code simply as:
$obj = new Thing();
And if Thing is defined in specific_modules, it will use that one, else the default one.
$normal_dir = 'modules';
$specific_dir = 'specific_modules';
$modules = array('module1' => array('file1.php','file2.php','file3.php'));
foreach($modules as $module => $files)
{
foreach($files as $file)
{
if(!file_exists("$specific_dir/$module/$file"))
{
include("$normal_dir/$module/$file");
}
else
{
include("$specific_dir/$module/$file");
}
}
}
This code will work as simply for you as possible, it makes it easy to add new files to your modules and change the directory names. By "load" I am making the assumption you mean include, but that part is easy enough to change.
Similarly to Alex's answer, you could also define an __autoload function:
function __autoload($class_name) {
if (file_exists(__DIR__ . '/specific_modules/' . $class_name . '.php')) {
require __DIR__ . '/specific_modules/' . $class_name . '.php';
}
elseif (file_exists(__DIR__ . '/modules/' . $class_name . '.php')) {
require __DIR__ . '/modules/' . $class_name . '.php';
}
else {
// Error
}
}
Then if you do $obj = new Thing(); it will try to load Thing.php from those two directories.
I want to use a single Doctrine install on our server and serve multiple websites. Naturally the models should be maintained in the websites' modelsfolder.
I have everything up (and not running) like so:
Doctrine #
/CustomFramework/Doctrine
Websites #
/var/www/vhosts/custom1.com/
/var/www/vhosts/custom2.com/
Generating works fine, all models are delivered in /application_folder/models and /application_folder/models/generated for the correct website.
I've added Doctrine::loadModels('path_to_models') in the bootstrap file for each website, and also registered the autoloaded.
But....
This is the autoloader code:
public static function autoload($className)
{
if (strpos($className, 'sfYaml') === 0) {
require dirname(__FILE__) . '/Parser/sfYaml/' . $className . '.php';
return true;
}
if (0 !== stripos($className, 'Doctrine_') || class_exists($className, false) || interface_exists($className, false)) {
return false;
}
$class = self::getPath() . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
if (file_exists($class)) {
require $class;
return true;
}
return false;
}
Am I stupid, or is the autoloader really doing this:
$class = self::getPath() . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
or, in other words: Does it require me to have ALL my generated doctrine classes inside the Doctrine app directory? Or in other words, do I need a single Doctrine installation for each website?
I'm getting an error that the BaseXXX class cannot be found. So the autoloading doesn't function correctly. I really hope i'm doing something wrong.. anyone?
Starting from Doctrine 1.2, the models autoloading is dealt by another autoloader, Doctrine_Core::modelsAutoload. This allows you to separate your library loading and models loading (if you want to use some other autoloader for loading the library classes).
In my include_path on the server-side I have a reference to a pear directory, in '/usr/share/pear/'. Within my applications I include files from a common library, living in '/usr/share/pear/library/' with require_once 'library/file.php'.
I've recently started using the spl autoloader, I noticed in the loader function you have to determine the logic with which to include the file. My first way of doing this was trying to include a file and suppressing it with # to see if it would fail, e.g. #include 'library/file.php' however I think mostly because I read a lot about # being a bad practice I decided to manually do the work myself by exploding get_include_path by the PATH_SEPARATOR and seeing if the directory is what I want it to be, then doing a file_exists and including it.
Like so:
function classLoader( $class ) {
$paths = explode( PATH_SEPARATOR, get_include_path() );
$file = SITE_PATH . 'classes' . DS . $class . '.Class.php';
if ( file_exists( $file) == false )
{
$exists = false;
foreach ( $paths as $path )
{
$tmp = $path . DS . 'library' . DS . 'classes' . DS . $class . '.Class.php';
if ( file_exists ( $tmp ) )
{
$exists = true;
$file = $tmp;
}
}
if ( !$exists ) { return false; }
}
include $file;
}
spl_autoload_register('classLoader');
Did I go the wrong route? Should I have just done the #include business or am I doing it somewhat in the right direction?
One thing that the Habari project autoloader does that's interesting is cache the whole class file list in memory so that it's not doing disk searches for the files every time a class is requested.
Essentially, you declare a static inside your __autoload() that holds an array of all of the class files, indexed by the class that will cause them to load. For example, the code would use Dir or glob() to generate this static array:
$class_files = array(
'user' => '/var/www/htdocs/system/classes/user.class.php',
);
Then you simply include $class_files[$class] to get the correct file. This is nice and speedy because it gets the catalog from the disk all at once, rather than generating the list or searching for a specific filename each time a new class is referenced. (You would be surprised how much of a speed difference it makes.)
If the class name isn't a key in the array, you can throw a custom exception or generate a stub/mock class to return. Also, if you check out the Habari system autoloader, you'll see that Habari implements __static() in classes that are autoloaded, which is like a constructor for static classes.
include_once() is to be avoided, and the # operator is unnecessary if you've checked for the file to include.
I personally go the way using
function autoload($class) {
/* transform class name into filename ... */
include $class;
}
even without the # to ease debugging (errors are shut down/logged in production)
You might also be interested in the related discussion on the PHP developer list: http://marc.info/?t=125787162200003&r=1&w=2