So I have an idea, but I'm thinking I need to run it by StackOverflow before I do something stupid.
I want to have an associative array of class names => filepaths. If PHP ever runs into a fatal error where the class is not defined, it will check if the key exists in my array and then require_once the class. This will prevent unnecessary bulk loading of classes that may never be used.
Bad idea?
How about trying PHP's built in autoloading.
Autoloading is the right way to do it, but spl_autoload_register is a cleaner way than __autoload, because it allows multiple autoloaders. Function __autoload also AFAIK stops working when spl_autoload_register is called, unless __autoload is also registered.
You can write your own autoload or use an existing one. For example, Zend Framework has an autoloader that uses conventions (Foo_Bar is in Foo/Bar.php). Nette Framework has RobotLoader, that indexes your classes and uses the index when neccessary. However, unless you use other things from the framework, it is probably too large.
see: http://www.php.net/manual/en/function.spl-autoload-register.php
If you are on PHP5, you can use __autoload().
makes your code a bit more manageable , although performance-wise, it's a bad choice. But I wouldn't worry it unless I'm building a Facebook.
What you are trying to do is already handled by the php __autoload function. You can read all about it here: http://php.net/manual/en/language.oop5.autoload.php
So, not a bad idea at all ;)
you should use autoloading with specified clas name structure, here is an example
the class names should should be only alpha and _ case-insensitive.
Lets take this directory structure and files
/classes/class.php
/classes/input/input.php
/classes/output/output.php
/classes/regex/regex.php
/classes/interface/parser/interface_parser.php
/classes/parser/parser.php
/classes/parser/xml/parser_xml.php
/classes/parser/html/parser_html.php
having the structure like this is good as it encourages you to code better when it comes to OOP.
Now if we take a look at the /classes/parser/html/html_parser.php file:
class Parser_Html extends Parser implements Interface_Parser
{
//looks nice in here
}
usually you would have to make sure the interface and the extended class is loaded, but these get autoloaded as well if they have not already.
creating the auto load system for this is not that complex, its just 1 function.
function __autoload($name)
{
//Classes
$parts = explode('_',strtolower($name));
$path = '/classes/';
foreach($parts as $p)
{
$path .= $p;
}
$path .= '/' . $name . '.php';
if(file_exists($path))
{
require_once $path;
}
}
so instead of including the class file first just run the class initiation.
$HtmlParser = new Parser_Html();
as the file has not been include the __autoload is run with a param of the class name, the autoload then looks in the directory that's relevant to the class name to try and load it.
also as your using the extend keyword in the class file shown above the class that is to be the parent gets run threw trhe autoloader aswell so you do not need to pre-load interfaces and classes etc.
Hope this helps you.
Note:
All code provided is untested and written for informational purposes, I would recommend you research the techniques more in detail before any implementation is done.
Related
I have 3 files.
vehicleClass.php
motorbikeClass.php (extends vehicleClass)
index.php
My question is... How do I connect all 3. On my index page, do I have to have
include 'classes/vehicleClass.php';
Do I need to have an include for the motorbike class or will the extend (inheritence) cover this?
You can let php autoload your files, by registering your own autoload function. If you have, in example, all your class files in the directory DOCROOT . 'classes' (DOCROOT being your document root), you can use a function like this (example):
function class_loader($class) {
include DOCROOT . 'classes/' . $class . '.class.php';
}
spl_autoload_register('class_loader');
Now if you try to create an object of class 'foo', it will try to include the file DOCROOT . '/classes/foo.class.php' before creating the object. You might want to extend the function a bit, eg. to lowercase file names (include DOCROOT . 'classes/'. strtolower($class) .'.class.php';). This way you can place class Foo in foo.class.php.
Short answer: a class that is extending (or otherwise using) another class must already have defined the parent class before the definition of the child class. Your assumption is correct, your VehicleClass must be included (or better, require'd) prior to your definition of MotorBike class.
However, most frameworks don't go about and include every depedency before all class definitions. This would become unwieldy on any system that has any amount of complexity to it. Instead, the developers of PHP have provided methods for autoloading classes. Using spl_autoload_register will allow you to write a function that will attempt to load in the source file for a given class whenever it is referenced but a definition for it has not yet been found.
Furthermore, once you get a system together that becomes complex, you don't want to store all of your files in a single place. Most frameworks leverage the filesystem and namespaces to help better organize all of their classes. Because of this, the PSR-0 standard was developed in order to help facilitate autoloading between frameworks. Take a look at this question for examples of PSR-0 compliant autoloaders.
Example of PSR-0 compliant class:
<?php namespace Vendor\Package;
class ClassName { }
This file would live in the filesystem at /Vendor/Package/ClassName.php
What you have to do is include 2 files in the index.php.
For example, your index.php page could be something like this.
<?php
require 'classes/vehicleClass.php';
require 'classes/motorbikeClass.php';
// Assuming your class name is MotorBike
$motorBike = new MotorBike();
// And just call the method you want, for example If you have a method called bikeName
echo $motorBike->bikeName();
?>
I hope you get an idea now.
P/S: I prefer require over include. :) Include() should work fine too.
My application uses a 'central' page controller that grabs a bunch of files (I hesitate to say libraries) each containing a few related classes, using require_once, before fetching the request. As in:
require_once (dir_lib . 'db.php');
require_once (dir_lib . 'uuid.php');
require_once (dir_lib . 'data.php');
require_once (dir_lib . 'token.php');
require_once (dir_lib . 'logs.php');
require_once (dir_lib . 'time.php');
etc...
I haven't bothered to check memory usage until recently installing the (awesome, but gigantic) 'HTML Purifier' library and glancing at its autoloader. Apparently, autoloader on not, each script instance now weighs in at a whopping (sweet jesus!) 5376 kilobytes memory usage. (I have no idea what the built-in autoloader is for if that's the end result, but I digress) Without the HTML Purifier, most instances still weight in at over a megabyte.
Reading about the PHP autoload functions I'm getting the impression that the autoloader is meant strictly for OOP. With the exception of the purifier library, I'm using very little object-oriented code. Am I just misunderstanding the concept? Is there any other practical way to avoid blindly loading a bunch of classes that may not be needed for each request? Am I just being lazy for trying to include them all up-front?
EDIT -
Repeating this comment up up here to clarify what I meant by non-oo, if that makes much difference:
I am
basically using classes in lieu of
namespaces without using (hardly) any
actual OOP. That is to say, the
"DBFunctions" class may contain, for
example, the functions "execute" and
"getRow". The functions are invoked
with a static method call such as
"dbFunctions::execute($sql)."
Is there a particular reason for those function files to not be classes? The only one I can think of would be PHP4 compatibility, but autoload doesn't exist in PHP4 anyway.
Just wrapping those existing code files in class DBFunctions { ... } and prefixing your function calls with DBFunctions:: to match should be enough to let the autoloader go to work (after setting an spl_autoload_register() callback to suit), and it's worth it. It's still stylistically procedural if you wanted to keep the code that way, and a decent step towards OOPifying your whole code base should you want to go that way.
(And HTML Purifier is an elephant, have you checked out PHP's internal filter functions?)
Using Autoload shoudn't hit performance as the class them selves aren't loaded until they are called or used. Autoload contruct basically loads class as needed is they haven't been instanciated already.
Find in the code where the class is been instanciated first. Unless class files are instanciated automatically, it shoudn't take any memory. Maybe not all are been instanciated, but not unset when not needed.
When you find where each is been instanciated or the one in question you can use then:
//Gets the object name using the class.
echo get_class($classinstance) , "\n";
You can handle it by placing some break points, using memory_get_peak_usage().
//Output memory usage at start of class instanciation.
echo memory_get_usage()
#memory_get_peak_usage() prior to 5.2.1.
$classobject1 = new theclass();
//Output memory usage after class instanciation.
echo memory_get_usage()
Now try the same thing but this time using unset() after class have been used:
//Output memory usage at start of class instanciation.
echo memory_get_usage()
#memory_get_peak_usage() prior to 5.2.1.
$classobject1 = new theclass();
unset($classobject1);
//Output memory usage after class instanciation.
echo memory_get_usage()
So basically in theory, unset any unused instance across app, find large objects, debug.
This is to clarify #greg's comments regards OOP:
Take this chunk as sample:
class Tun{
private $run = 'i\'m running';
function run(){
echo $this->run;
}
}
echo Tun::run;
Output:
Error: Fatal error: Undefined class constant 'run' in C:\Work\pro\debug.php on line 16
The above sample because is referencing a function which uses OOP, in this case the class private variable $run. Since the class isn't instantiated(an object instance), it will error out. So, yes you can use functions inside of a class by reference but they mostly have to be plain procedural or constant references.
Hope this help.
PHP5's autoload is only for loading classes on the fly. Only one autoload function can be used in your application (but see below). Read the autoload docs for details. Basically, you can define an __autoload() function that will load any file you want (or do anything you want, for that matter), containing any classes you want, when PHP tries to call a class that isn't loaded yet.
The link that you provided in your question is about the Standard PHP Library's autoload, which is different, and more flexible. spl_autoload_register() lets you register a stack of autoload functions, rather than just one. This is most useful when you're using libraries in your code that make use of autoload, because their autoload doesn't have to clobber yours or those of other libraries.
If you're just starting out with OO on small projects, you probably just want __autoload(), but if you want to integrate libraries like HTMLPurifier, which does use spl_autoload, google for spl_autoload for the docs, they're the first results.
What I want to do is this:
When I write a class and the class instantiates another class, I want to import that class with an require_once. Just the way I do so in Objective-C. But instead of using plain require_once function and messing around with paths and string concatenation, I would prefer something like:
importClass('myclass');
but I'm afraid that it's impossible to write a function that will include code. If I would do that in the importClass() function, I would include the class code into the implementation block of the function, which of course is nonsense. So what options do I have here?
The cleanest way to do what you want looks to be to use the Autoloader
It's not impossible at all. You can write this function:
function importClass($class) {
require_once "$class.class.php";
}
The only caveat is that any global variables declared or used inside that file will now be local to importClass(). Class definitions however will be global.
I'm not sure what this really gives you however but you can certainly do it.
In my application I have a system base class which has a similar function. The import function takes a class name, looks in a couple of related directories and finds a matching name (I also did some stuff with extensions to libraries but you may not need that) and instantiates a class inside with the same name. Then it takes that new instance and sets it as an object in the system base class.
using autoload as other answers have suggested would probably work better in your situation but this is just another way to look at it.
You can accomplish something similar using a class autoloader. I would also make sure that your include_path is set properly and that you are using a directory structure that makes sense for your classes - it's generally a good practice to NOT depend on class autoloaders, and instead include classes based on their relative path to your include_path.
I'd highly recommend browsing through Zend Framework, particularly Zend_Loader, for a good (if not over-architected) implementation. Also notice that Zend Framework will work without an autoloader in place - each file calls require_once on its direct dependencies, using their nice, organized directory structure.
I am using Kohana and just found this piece of code in their autoloading method
// Class extension to be evaluated
$extension = 'class '.$class.' extends '.$class.'_Core { }';
// Start class analysis
$core = new ReflectionClass($class.'_Core');
if ($core->isAbstract())
{
// Make the extension abstract
$extension = 'abstract '.$extension;
}
// Transparent class extensions are handled using eval. This is
// a disgusting hack, but it gets the job done.
eval($extension);
basically what it does is when I am referring to a class that doesn't exist (through object instantiation, calling class_exists() etc.), Kohana will create a class (eg. Foo) that extends a library class that follows certain naming convention (eg. Foo_Core). just being curious, is there any way to do something similar but without the use of eval?
If you are looking to create a dynamic class then eval() is the goto function (pun intended.) However kind of related, I've found that you can put a class declaration within an if-then statement. So you can do the following:
if(true)
{
class foo
{
// methods
}
}
I use this to check to see if dynamically created classes (from a configuration file) are current... if so then load the class, otherwise... regenerate the class, and load the new one. So if you're looking to create dynamic classes for similar reasons this might be a solution.
I think you're stuck with eval() for that.
It's marked as a "disgusting hack" so that makes it OK :)
I'd be interested to know what you do with such an empty class...
If you were wanting to be able to cache your dynamically created classes, you could write out to a file and require it instead. This could be considered equally hack-ish, but it is an option. For classes that are created once and used often, it might be a good solution. For classes that need to be dynamic every time, sticking with eval is probably the best solution.
$proxyClassOnDisk = '/path/to/proxyCodeCache/' . $clazz .'.cachedProxyClass';
if ( ! file_exists($proxyClassOnDisk) ) {
// Generate the proxy and put it into the proxy class on disk.
file_put_contents($proxyClassOnDisk, $this->generateProxy($object));
}
require_once($proxyClassOnDisk);
In this example, the idea is that you are creating dynamic proxies for the class of $object. $this->generateProxy($object) will return the string that looks more or less like what $extension looks like in the original question.
This is by no means a complete implementation, just some pseudo code to show what I'm describing.
I'm wondering what the best practice is for handling the problem with having to "include" so many files in my PHP scripts in order to ensure that all the classes I need to use are accessible to my script.
Currently, I'm just using include_once to include the classes I access directly. Each of those would include_once the classes that they access.
I've looked into using the __autoload function, but hat doesn't seem to work well if you plan to have your class files organized in a directory tree. If you did this, it seems like you'd end up walking the directory tree until you found the class you were looking for. Also, I'm not sure how this effects classes with the same name in different namespaces.
Is there an easier way to handle this?
Or is PHP just not suited to "enterprisey" type applications with lots of different objects all located in separate files that can be in many different directories.
I my applications I usually have setup.php file that includes all core classes (i.e. framework and accompanying libraries). My custom classes are loaded using autoloader aided by directory layout map.
Each time new class is added I run command line builder script that scans whole directory tree in search for model classes then builds associative array with class names as keys and paths as values. Then, __autoload function looks up class name in that array and gets include path. Here's the code:
autobuild.php
define('MAP', 'var/cache/autoload.map');
error_reporting(E_ALL);
require 'setup.php';
print(buildAutoloaderMap() . " classes mapped\n");
function buildAutoloaderMap() {
$dirs = array('lib', 'view', 'model');
$cache = array();
$n = 0;
foreach ($dirs as $dir) {
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)) as $entry) {
$fn = $entry->getFilename();
if (!preg_match('/\.class\.php$/', $fn))
continue;
$c = str_replace('.class.php', '', $fn);
if (!class_exists($c)) {
$cache[$c] = ($pn = $entry->getPathname());
++$n;
}
}
}
ksort($cache);
file_put_contents(MAP, serialize($cache));
return $n;
}
autoload.php
define('MAP', 'var/cache/autoload.map');
function __autoload($className) {
static $map;
$map or ($map = unserialize(file_get_contents(MAP)));
$fn = array_key_exists($className, $map) ? $map[$className] : null;
if ($fn and file_exists($fn)) {
include $fn;
unset($map[$className]);
}
}
Note that file naming convention must be [class_name].class.php. Alter the directories classes will be looked in autobuild.php. You can also run autobuilder from autoload function when class not found, but that may get your program into infinite loop.
Serialized arrays are darn fast.
#JasonMichael: PHP 4 is dead. Get over it.
You can define multiple autoloading functions with spl_autoload_register:
spl_autoload_register('load_controllers');
spl_autoload_register('load_models');
function load_models($class){
if( !file_exists("models/$class.php") )
return false;
include "models/$class.php";
return true;
}
function load_controllers($class){
if( !file_exists("controllers/$class.php") )
return false;
include "controllers/$class.php";
return true;
}
You can also programmatically determine the location of the class file by using structured naming conventions that map to physical directories. This is how Zend do it in Zend Framework. So when you call Zend_Loader::loadClass("Zend_Db_Table"); it explodes the classname into an array of directories by splitting on the underscores, and then the Zend_Loader class goes to load the required file.
Like all the Zend modules, I would expect you can use just the loader on its own with your own classes but I have only used it as part of a site using Zend's MVC.
But there have been concerns about performance under load when you use any sort of dynamic class loading, for example see this blog post comparing Zend_Loader with hard loading of class files.
As well as the performance penalty of having to search the PHP include path, it defeats opcode caching. From a comment on that post:
When using ANY Dynamic class loader APC can’t cache those files fully as its not sure which files will load on any single request. By hard loading the files APC can cache them in full.
__autoload works well if you have a consistent naming convention for your classes that tell the function where they're found inside the directory tree. MVC lends itself particularly well for this kind of thing because you can easily split the classes into models, views and controllers.
Alternatively, keep an associative array of names to file locations for your class and let __autoload query this array.
Of the suggestions so far, I'm partial to Kevin's, but it doesn't need to be absolute. I see a couple different options to use with __autoload.
Put all class files into a single directory. Name the file after the class, ie, classes/User.php or classes/User.class.php.
Kevin's idea of putting models into one directory, controllers into another, etc. Works well if all of your classes fit nicely into the MVC framework, but sometimes, things get messy.
Include the directory in the classname. For example, a class called Model_User would actually be located at classes/Model/User.php. Your __autoload function would know to translate an underscore into a directory separator to find the file.
Just parse the whole directory structure once. Either in the __autoload function, or even just in the same PHP file where it's defined, loop over the contents of the classes directory and cache what files are where. So, if you try to load the User class, it doesn't matter if it's in classes/User.php or classes/Models/User.php or classes/Utility/User.php. Once it finds User.php somewhere in the classes directory, it will know what file to include when the User class needs to be autoloaded.
#Kevin:
I was just trying to point out that spl_autoload_register is a better alternative to __autoload since you can define multiple loaders, and they won't conflict with each other. Handy if you have to include libraries that define an __autoload function as well.
Are you sure? The documentation says differently:
If your code has an existing __autoload function then this function must be explicitly registered on the __autoload stack. This is because spl_autoload_register() will effectively replace the engine cache for the __autoload function by either spl_autoload() or spl_autoload_call().
=> you have to explicitly register any library's __autoload as well. But apart from that you're of course right, this function is the better alternative.
__autoload will work, but only in PHP 5.