I mean, if I have the class Map located in the map.php file, should I have this file as Map.php? 'Cause I made an autoloader:
spl_autoload_register(function ($class_name)
{
$CorePath = dirname(__DIR__) . '/fod/' . $class_name . '.php';
if(file_exists($CorePath))
{
include $CorePath;
}
});
now the problem is if I call: $x = new Map(); the autoloader doesn't locate it, but if I rename as Map.php the autoloader locate it. Any idea?
The classname must be the same as the filename, as you've said. If you want to stick with the lower case filenames, then make the $class_name lowercase.
The function is strtolower()
$CorePath = dirname(__DIR__) . '/fod/' . strtolower($class_name) . '.php';
But the file must have the same name as the class, so that the autoloader can work.
PHP is case-sensitive. So, yes, you should either use the lowercase version of $class_name to load files and use lowercase for the file names or have the class names uppercase. I would prefer the latter but this is a matter of taste.
Apart from everything else PHP class names (and namespaces) are not case sensitive. For PHP (8.1)
class Map {
...
}
and
class map {
...
}
are the same thing. Equally both new \Map() and new \map() will construct the same thing. If the class Map is in a class file names Map.class.php this will be no problem in a cases insensitive OS, (like for instance Windows, where AAMOF the NTFS is case sensitve!); map.class.php and Map.class.php will be equally the same thing. On a case sensitive OS/fs like Linux things will be dramatically different and care must be taken when autoloading class files as map.class.php and Map.class.php are two different things.
Using spl_autoload_extensions()
If the now preferred autoloading with
spl_autoload_extensions('.class.php');
spl_autload_register();
is used there is a important thing to realize. As spl_autoload_extensions is made aware of the fact that class names are not case-sensitive it will do a mb_strtolower to every class name it encounters. Suddenly a class named class Map can no longer be found in a file called Map.class.php. It has to be named map.class.php because of this mb_strtolower().
Options
There are two options.
ignore the PSR standard and use lower case for class name (reflecting the actual situation) and class filename.
apply the PSR standard naming but use all lowercase in the class filename.
Option 1) is good in a way that class name and class filename are in sync which IMO is preferable whereas Option 2) might confuse other developers reading your code. But neither is ideal. Btw. (PSR)[https://www.php-fig.org/psr/psr-4/] seems to ignore this fact of default PHP life and suggest their own class loader.
Related
I've set up a really basic autoloader in my index.php to grab a namespaced class within hello.php. My development environment is Ubuntu 12.04.
Why am I trying to do this? I'm trying to stick to the PSR-1 and PSR-2 coding standard, that includes:
Class names MUST be declared in StudlyCaps
Namespaces are as /Vendor/Class (note: capitals)
The following is my structure and code that works before making the changes to capitals.
Folder Structure
- web
-- index.php
-- core
--- hello.php
Autoloader
Within index.php, I have my autoloader:
set_include_path(__DIR__);
spl_autoload_extensions('.php,.class.php');
spl_autoload_register();
Class File
Within the core folder, I have hello.php
namespace core;
class hello {
public function __construct() {
echo 'Constructed!';
}
}
Code that works
If I run $obj = new \core\hello(); in my index.php, I get back "Constructed!". Great!
That which doesn't work
Renaming my core folder to 'Core' - note the uppercase C, and also the namespace in hello.php to namespace Core;.
Now let's try again with $obj = new \Core\hello();
Fatal error: Class 'Core\hello' not found in ...
So please tell me, why am I not able to use capital letters to keep inline with the PSR standards? What am I doing wrong?
When you run your PHP code on a Linux platform, it's important to remember that Linux is case sensitive with filenames.
This affects autoloaders because they typically use the namespace and the class name when building the filename to load.
If the folder is named core, then the namespace must be core, with the same capitalisation. If you change it to Core in the namespace, then you must do the same to the folder name. (and as a result, all other core classes must be changed to Core at the same time).
On Windows, this doesn't happen because the Windows filesystem isn't case sensitive. This can cause confusion when code is tested and works on a local Windows-based dev system, and then breaks when it is copied to a Linux-based server.
[EDIT]
Okay, so I missed that you had changed the dirname as well. But nevertheless, I still think this is an issue of the filename/dirname case.
I note that you're calling spl_autoload_register() without any params. This means that the default spl_autoload() function will be used as the autoloader.
If you read the documentation for spl_autoload(), you'll note that it uses the lowercased version of the class and namespace.
In other words, using the default autoloader, your classes can be mixed case, but the folder structure and filenames must be all lower case.
So in fact, for you, you need to keep your filenames lower case.
I've personally experienced it the other way round, as per my original answer, where I had a fully lower case filename, and my mixed case class name was breaking when I moved from Windows dev box to Linux server. The reason my experience is different from yours is because I'm using a custom-written autoload function, which doesn't do an auto-lowercase conversion, so the case of my filenames has to match that of my classnames.
I think you have shown us some good ambiguity.Correct me if I am wrong.
According to the specification you have to use the lowercased name of the class (and namespace) being instantiated.(http://www.php.net/manual/en/function.spl-autoload.php)
But PSR tells us to use the capital letters. If you want to stick with PSR then we have to overwrite the default spl_autoload to our own.
For anyone else having issues with this, why not make use of ucfirst() or strtolower() ?
So the code below would try all lowercase and also try first letter uppercase files
e.g: somename.class.php or Somename.class.php
The is_readable() checks first as to not display the php error for file not found.
spl_autoload_register(function($name) {
if (is_readable(strtolower($name).'.class.php')) {
require_once(strtolower($name).'.class.php');
}
elseif (is_readable(ucfirst($name).'.class.php')) {
require_once(ucfirst($name).'.class.php');
}
});
This previous question shows how to force autoloading of all classes.
However I need to force autoloading of just one class on its own. How could I do that?
It mustn't:
Involve changing the class's source code
Rely on any part of the class's source code (methods, variables, modifiers e.g. it must be free to change from concrete to abstract without affecting this).
Also, preferably it would not involve coding the class's name as a string. (To help with IDE refactoring and so on).
The best option I have found so far would be to just use spl_autoload_call():
spl_autoload_call("Jodes\\MyClass");
or for non-namespaced classes:
spl_autoload_call("MyClass");
I had the same need recently. Doing require_once was not an option because the class really needed to be located by the autoloader because of some more complex rules, there was no way to know exactly the path to the file with the class.
Although the function spl_autoload_call($classname) is designed to do precisely this, it suffers from a fundamental flaw: it will throw a FATAL ERROR in case you call it twice for the same classname or even if some of the child classes of that class were already loaded. This happens because it's not possible to redeclare classes in PHP:
<?php
spl_autoload_call('TheClass');
// no problem
spl_autoload_call('TheClass');
// PHP Fatal error: Cannot declare class TheClass, because the name is already in use in ... on line ...
My solution for this problem is no rely on the side-effect of class_exists($classname) which was not designed for this purpose, but is more configurable and, therefor, offers more control about triggering the autoloader.
Even better, it has absolutely no problem on multiple calls or if something was already loaded in the inheritance chain. It simply has the side-effect (if you want) of requiring the file, if the class is not there yet!
<?php
class_exists('TheClass');
// no problem
class_exists('TheClass');
// already exists. No need to autoload it and no fatal errors!
With this you have a safe and idempotent way to load the class through the autoloader.
And, if you don't want to hard-code the string with the classname in there, from PHP version 5.5 onward, you can use the ::class pseudo constant, which is resolved at compile time to the string with the fully qualified classname (including namespaces):
<?php
class_exists(TheClass::class);
I had exactly the same problem and tried different autoload functions. On a Windows PHP 8.1.5 version I had these two different autoloaders:
set_include_path(get_include_path() . PATH_SEPARATOR . 'class/');
spl_autoload_extensions('.class.php');
spl_autoload_register();
or
$autoloader = function( string $class_name )
{
$filename = $_SERVER["DOCUMENT_ROOT"].'/class/' . str_replace( '\\', '/', $class_name) . '.class.php';
require_once $filename;
};
// our class loader
spl_autoload_register( $autoloader );
which seemingly do the same thing.
As one class file in my application also create some supporting global function variables that can be used during construction of the said class I needed to force the class file loading before accessing said functions variables and construction. I did this with the class_exist() on the specific class.
Using the spl_auoload_extenions version this worked fine; using the spl_autoload_register($autoloader) version causes an undefined variable when using the function variables.
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.
I've set up a really basic autoloader in my index.php to grab a namespaced class within hello.php. My development environment is Ubuntu 12.04.
Why am I trying to do this? I'm trying to stick to the PSR-1 and PSR-2 coding standard, that includes:
Class names MUST be declared in StudlyCaps
Namespaces are as /Vendor/Class (note: capitals)
The following is my structure and code that works before making the changes to capitals.
Folder Structure
- web
-- index.php
-- core
--- hello.php
Autoloader
Within index.php, I have my autoloader:
set_include_path(__DIR__);
spl_autoload_extensions('.php,.class.php');
spl_autoload_register();
Class File
Within the core folder, I have hello.php
namespace core;
class hello {
public function __construct() {
echo 'Constructed!';
}
}
Code that works
If I run $obj = new \core\hello(); in my index.php, I get back "Constructed!". Great!
That which doesn't work
Renaming my core folder to 'Core' - note the uppercase C, and also the namespace in hello.php to namespace Core;.
Now let's try again with $obj = new \Core\hello();
Fatal error: Class 'Core\hello' not found in ...
So please tell me, why am I not able to use capital letters to keep inline with the PSR standards? What am I doing wrong?
When you run your PHP code on a Linux platform, it's important to remember that Linux is case sensitive with filenames.
This affects autoloaders because they typically use the namespace and the class name when building the filename to load.
If the folder is named core, then the namespace must be core, with the same capitalisation. If you change it to Core in the namespace, then you must do the same to the folder name. (and as a result, all other core classes must be changed to Core at the same time).
On Windows, this doesn't happen because the Windows filesystem isn't case sensitive. This can cause confusion when code is tested and works on a local Windows-based dev system, and then breaks when it is copied to a Linux-based server.
[EDIT]
Okay, so I missed that you had changed the dirname as well. But nevertheless, I still think this is an issue of the filename/dirname case.
I note that you're calling spl_autoload_register() without any params. This means that the default spl_autoload() function will be used as the autoloader.
If you read the documentation for spl_autoload(), you'll note that it uses the lowercased version of the class and namespace.
In other words, using the default autoloader, your classes can be mixed case, but the folder structure and filenames must be all lower case.
So in fact, for you, you need to keep your filenames lower case.
I've personally experienced it the other way round, as per my original answer, where I had a fully lower case filename, and my mixed case class name was breaking when I moved from Windows dev box to Linux server. The reason my experience is different from yours is because I'm using a custom-written autoload function, which doesn't do an auto-lowercase conversion, so the case of my filenames has to match that of my classnames.
I think you have shown us some good ambiguity.Correct me if I am wrong.
According to the specification you have to use the lowercased name of the class (and namespace) being instantiated.(http://www.php.net/manual/en/function.spl-autoload.php)
But PSR tells us to use the capital letters. If you want to stick with PSR then we have to overwrite the default spl_autoload to our own.
For anyone else having issues with this, why not make use of ucfirst() or strtolower() ?
So the code below would try all lowercase and also try first letter uppercase files
e.g: somename.class.php or Somename.class.php
The is_readable() checks first as to not display the php error for file not found.
spl_autoload_register(function($name) {
if (is_readable(strtolower($name).'.class.php')) {
require_once(strtolower($name).'.class.php');
}
elseif (is_readable(ucfirst($name).'.class.php')) {
require_once(ucfirst($name).'.class.php');
}
});
I am using this class in php for autoloading.
http://pastebin.com/m75f95c3b
But when I have somewhere
class Bar extends Foo
And I have a file called foo.class.php it won't find the class.
But when i chagne the filename to Foo.class.php it will find the class.
I am trying to add some functionallity to my class to always find the file if its there no matter wether the filename starts with a capital or not. But so far I haven't scuceeded.
Anyone?
If all of your file have lowercase names, apply strtolower to the $className variable value in each method of your class:
$className = strtolower($className);
But I suggest that you draft some coding guidelines that every developer has to stick to. Otherwise you will have to test each of the 2<length of file name> possibilities of writing a file name using uppercase and lowercase letters.
Gumbo's solution is the best one and that's what almost everyone uses. I do not understand why you do not like. Of course you can first check whether a file with the class name capitalized exists or not, and if not then check whether lowercased version exists or not. That's definitely not better than Gumbo's solution. After all "your" programmers have to follow some conventions/rules.
I'd do it more or less like this
function __autoload($class_name) {
$upperName = strtoupper(substr($class_name,0,1)) . substr($class_name, 1);
$lowerName = strtolower(substr($class_name,0,1)) . substr($class_name, 1);
//Check to see if the class file exists as-is
if(file_exists($class_name . '.php')) {
require_once($class_name . '.php');
}elseif(file_exists($upperName . '.php')) {
require_once($upperName . '.php');
}elseif(file_exists($lowerName . '.php')) {
require_once($lowerName . '.php');
}else {
throw new Exception("The class $class_name could not be found.");
}
}
It's easy to enough to add other conditionals as different naming conventions arise.
I use an __autoload() function that takes into account several different file naming conventions to provide backwards compatibility with previous developers.
It does a simple loop over each convention and checks to see if that file exists. If the file exists, then it loads it.
For my function, I do this for different file extensions, such as .inc, .class or .inc.php . You could do the same, but search for upper and lower first characters.
I would put this in the searchForClassFile() method, in the else part with the comment 'Found a file'.
EDIT (more information):
Rather than recursively descend into a class directory looking for the correct file, I map the class name to a specific location. This is a common practice. For example, foo_bar is mapped to [CLASS_DIRECTORY]/foo/bar.[EXTENSION]. In our case, we check several different extensions.
In your case, you have to make a design decision about how you want to search for the class file, but modifying your code:
} else {
// Found a file
if ($f == $className . self::$classFileSuffix ||
$f == strtolower($className) . self::classFileSuffix) {
return $subPath;
}
}
Instead of strtolower() you could write a function that only lowers the first character, or if using PHP > 5.3.0 (not officially released) use the lcfirst() function.
You can use an autoloader that scans the directories for .php files, and actually looks into the contents of each file for patterns such as "class Foo" using a regular expression. You will need to cache this, of course, preferably by using an array of the form class => directory. You also need to make sure you invalidate the cache correctly, e.g. when there are new .php files in the folder.
I do this for my website, and it works great.