I have been trying to load the various classes associated with PhpSpreadsheet, but am encountering many problems.
I have followed many postings and implemented spl_autoload_register using the following code:
function my_autoload($classname) {
$fullclassname = $classname . '.php';
include $fullclassname;
}
set_include_path(get_include_path() . PATH_SEPARATOR . '/var/www/html/mywebsite/includes/PhpOffice/PhpSpreadsheet/');
spl_autoload_register('my_autoload');
use PhpOffice\PhpSpreadsheet;
Unfortunately, all I every get is 'Class not found'.
I've discovered that a limitation of spl_autoload is that it requires everything to be in lower case. As I understand it, this would require every directory name, file name, Class name and Namespace definition within PhpSpreadsheet to be modified. Not a viable long term solution.
Another alternative is to hard-code include statements for each .php file within PhpSpreadsheet. Again, not a viable long term solution.
Is there another option?
Related
I am getting more and more confused as I read through various SO posts and the PHP Manual on how to properly set up the "USE" for PHPSpreadsheet. I installed the files manually (not through Composer) and all the posts/references I've read seem to imply that I should set it up this way:
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
My folder structure does not follow this format and is set up this way (for example):
/phpscripts/phpspreadsheet/IOFactory.php
I'm now wondering if the folder structure is not related to how the "USE" is configured/utilized but I have not found any posts that explain this for my level of understanding. I see a few references to the use of "REQUIRE" that points to the exact location of the specific files but that is just adding to my confusion. Could someone elaborate on how this should be properly set up?
The thing that composer does for you is to provide what is called an autoloader, which basicly loads the files which contain the classes that your code needs at runtime.
Registering a autoloader is done through the function spl_autoload_register where you provide it a callable that should be run when your code requests a class that is not known to PHP.
So when using a composer package without composer you would need to make your own autoloader for that specific library.
As you noticed the FQCN(fully qualified class name) aka. (namespace + class name) does not always correspond to the location of the file containing that class. The namespace of a class is actually defined in the file containing the class(typically in the top of the file).
PSR-4 is a standard that:
describes a specification for autoloading classes from file paths.
and if you look in the PhpSpreadsheet composer.json you can see that they follow that standard so that makes it easier to implement our own autoload now that you have decided not to use composer.
here is my attempt at loading the PhpSpreadsheet without composer
use PhpOffice\PhpSpreadsheet\IOFactory;
spl_autoload_register(function ($class) {
$path = 'phpscripts/phpspreadsheet'; // Replace this with the path to where PhpSpreadsheet is stored.
//Split the FQCN into parts
$fqcnParts = explode('\\', trim($class, " \t\n\r\0\x0B\\"));
//check that we have atleast 3 parts and that the vendor namespaces matches our package
if (count($fqcnParts) < 3 || $fqcnParts[0] != 'PhpOffice' || $fqcnParts[1] != 'PhpSpreadsheet') {
return;
}
// read the comments in this next parts from the inside -> out
$filePath =
rtrim($path, '/') . '/' . // prefix with our path to the library
ltrim(
implode( // implode the remaining parts into a string split by /
'/',
array_slice($fqcnParts, 2) // Remove the vendor namespaces
),
'/'
) . '.php'; // suffix the file extension
if (file_exists($filePath)) {
include $filePath;
}
});
var_dump(class_exists(IOFactory::class)); // this line will try to autoload the IOFactory class
Hope this helps you otherwise feel free to hit me up :-)
Many developers writing object-oriented applications create one PHP
source file per class definition. One of the biggest annoyances is
having to write a long list of needed includes at the beginning of
each script (one for each class).
In PHP 5, this is no longer necessary. The spl_autoload_register()
function registers any number of autoloaders, enabling for classes and
interfaces to be automatically loaded if they are currently not
defined. Source: http://php.net/manual/en/language.oop5.autoload.php
Well i found that this statement is not true, because i still end up writing
long list of includes imports in each file simply because i am using different sub folders inside my includes folder and namespaces according to PHP-FIG's PSR-0 coding convention.
includes/core/database/
includes/core/html/
includes/domain/
etc.
spl_autoload_register() unable to automatically load DB, HTML Domain logic classes because it does not know folder structure where file is so i am using namespaces to it, but it takes just as much space as having imports on top of every script.
use MyProject\Core\Database;
use MyProject\Core\Html;
use MyProject\domain;
I use different classes per script so i cannot simply make one big file and include_once(), besides importing of namespaces does not work with include_once().
I instantiate class like this
try {
$DBQuery = new Database\DBQuery();
$HtmlGenerator = new Html\HtmlGenerator();
$domain = new domain\UserRegister();
} catch (Error $e) {
echo $e->getMessage();
}
My Autoload function
spl_autoload_register(function ($fullyQualifiedClassName) {
//change backslash in namespace name to DIRECTORY_SEPARATOR for file system
if ( stristr($fullyQualifiedClassName, "\\") ) {
$fullyQualifiedClassName = str_ireplace("\\", DIRECTORY_SEPARATOR, $fullyQualifiedClassName);
}
//function dirname() used because THIS file in sub folder /includes and we need to go to parent folder
$class_path = $lib_patch . DIRECTORY_SEPARATOR . "{$fullyQualifiedClassName}.php";
if ( !is_file($class_path) ) {
throw new Error("Unable to load class with path: $class_path");
}
require_once $class_path;
});
Any way i can avoid importing multiple namespaces at this i am open to stopping using namespaces completely but i'd like to keep my sub folder structure. Is there way auto-load function can know what folder my files at without making code that will loop trough every sub-folder looking for file e.g. DBQuery.php because this will impact performance.
Autoloading is saving you from includeing the files, you're now exclusively dealing with name resolution. If you don't want to write a bunch of use statement in your files, you could simply use the fully qualified names of those classes instead of aliasing them:
$db = new \MyProject\Core\Database;
$html = new \MyProject\Core\Html;
...
The use of use MyProject\Core\Database is that it enables you to write Database instead of \MyProject\Core\Database. Autoloading of the underlying file works the same.
If you even don't like that aspect, then it's hard to have your cake and eat it too. You could flatten your namespaces so you don't have as many different namespaces to import, but then your project organisation starts to become more prone to name clashes or harder to locate files. It's a tradeoff, something has to give somewhere. If you're not happy with some consequence of using namespaces, you need to find a new happy middleground for yourself.
Having said this, in many languages it is extremely common to have a bunch of import statements at the top of each file in one way or another. A decent IDE can largely auto-generate those while you write your code. It is something that you should rather get used to instead of fighting against it. It may be annoying, but the alternatives are more name clashes or giant imports. It's virtually impossible to have it modular, fast, extensible and terse all at the same time.
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've just started to build my very own MVC framework. Feel's kind of nice to know everything from the ground up and only get the stuff that's really necessary for my app.
I come from a codeIgniter background which helped me to get into the MVC perspective of seeing things. In codeigniter, to include a file, codeIgniters very own load class is used.
This load class, when loading a file, checks if a file have previously been included, and if not includes it, which ensures that a file isn't included twice.
However this method of including files has the downside of making it impossible (?) to run tests on my files or take advantage of PHPdoc in my IDE, which I need.
Clearly, I can use the normal include & require functions in PHP forwards and backwards across my application, where a certain library would be needed, but it clearly won´t be good to possible include the same file twice...
So - what's a good solution to use in my own PHP5 MVC framework to include files?
I'm doing a similar thing to you, and I'm using autoload.
Just put this in index.php:
function __autoload($class_name) {
include $class_name . '.php';
}
or include whatever logic you need to check multiple directories.
Edit: Here's my (slightly flaky) code for checking an alternate path. It could be done a lot more cleanly if you were going to need to check multiple paths.
function __autoload($class_name) {
$path = INC_PATH . strtolower($class_name) . '.php';
if (!file_exists($path)) {
$path = MODELS_PATH . strtolower($class_name) . '.php';
}
require $path;
}
just use include_once and require_once . while CI has a load class, you're by no means required to use it.
use include_once/ require_once but the best solution would be working with a autoloader
http://php.net/manual/de/language.oop5.autoload.php
what IDE do you use?
Generally php has the built in include_once which as the name says includes the file, but only once.
Or you can use autoloading which most IDEs support (though some need a bit of a hint - depends on what IDE are you using).
I've always struggled with how to best include classes into my php code. Pathing is usually an issue but a few minutes ago i found this question which dramatically helps that. Now I'm reading about __autoload and thinking that it could make the process of developing my applications much easier. The problem is i like to maintain folder structure to separate areas of functionality as opposed to throwing everything into a general /lib folder. So if i override autoload to do a deep search of a class folder including all subfolders, what performance hits can i expect?
Obviously this will depend on scale, depth of the folder structure and number of classes but generally I'm asking on a medium scale project will it cause problems.
__autoload is great, but the cost of stating all the files in a recursive search function is expensive. You might want to look at building a tree of files to use for autoloading. In my framework, I consistently name files for their classes and use a map that is cached for the data.
Check out http://trac.framewerk.org/cgi-bin/trac.fcgi/browser/trunk/index.php [dead link] starting at line 68 for an idea of how this can be done.
Edit: And to more directly answer your question, without caching, you can expect a performance hit on a site with medium to heavy traffic.
A common pattern (Pear, Zend Framework as examples...) is to make the classname reflect the path, so Db_Adapter_Mysql will be in at /Db/Adapter/Mysql.php, from somewhere that's added to the include-path.
There are 2 ways that you could easily do this, first of all, name your classes so that they'll define the structure of where to find them
function __autoload($classname)
{
try
{
if (class_exists($classname, false) OR interface_exists($classname, false))
{
return;
}
$class = split('_', strtolower(strval($classname)));
if (array_shift($class) != 'majyk')
{
throw new Exception('Autoloader tried to load a class that does not belong to us ( ' . $classname . ' )');
}
switch (count($class))
{
case 1: // Core Class - matches Majyk_Foo - include /core/class_foo.php
$file = MAJYK_DIR . 'core/class_' . $class[0] . '.php';
break;
case 2: // Subclass - matches Majyk_Foo_Bar - includes /foo/class_bar.php
$file = MAJYK_DIR . $class[0] . '/class_' . $class[1] . '.php';
break;
default:
throw new Exception('Unknown Class Name ( ' . $classname .' )');
return false;
}
if (file_exists($file))
{
require_once($file);
if (!class_exists($classname, false) AND !interface_exists($classname, false))
{
throw new Exception('Class cannot be found ( ' . $classname . ' )');
}
}
else
{
throw new Exception('Class File Cannot be found ( ' . str_replace(MAJYK_DIR, '', $file) . ' )');
}
}
catch (Exception $e)
{
// spl_autoload($classname);
echo $e->getMessage();
}
}
Or, 2, use multiple autoloaders. PHP >=5.1.2 Has the SPL library, which allows you to add multiple autoloaders. You add one for each path, and it'll find it on it's way through. Or just add them to the include path and use the default spl_autoload()
An example
function autoload_foo($classname)
{
require_once('foo/' . $classname . '.php');
}
function autoload_bar($classname)
{
require_once('bar/' . $classname . '.php');
}
spl_autoload_register('autoload_foo');
spl_autoload_register('autoload_bar');
spl_autoload_register('spl_autoload'); // Default SPL Autoloader
Autoload is great PHP feature that helps you very much...
The perfomance wouldn't suffer if will use the smart taxonomy like:
1. every library stays in the folders "packages"
2. every class is located by replacing the "_" in the class name with the "/" and adding a ".php" at the end
class = My_App_Smart_Object
file = packages/My/App/Smart/Object.php
The benefits of this approach(used by almost any framework) is also a smarter organization of your code :-)
Hunting for files all over the place will make things slower (many more disk hits). Loading all of your classes in case you might need them will make things take more memory. Specifying which classes you need in every file is difficult to maintain (i.e. they don't get removed if they're no longer used).
The real question is which of these is more important to you? They're all tradeoffs, in the end, so you have to pick one. It's arguable, though, that most of the overhead in the second and third options has to do with actually compiling the code. Using something like APC can significantly reduce the overhead of loading and compiling every class on every page load.
Given the use of APC, I would likely take the approach of dividing up my code into modules (e.g. the web interface module, the database interaction module, etc.) and have each of those modules import all the classes for their module, plus classes from other modules they may need. It's a tradeoff between the last two, and I've found it works well enough for my needs.
I tend to use a simple approach where __autoload() consults a hash mapping class names to relative paths, which is contained in a file that's regenerated using a simple script which itself performs the recursive search.
This requires that the script be run when adding a new class file or restructuring the code base, but it also avoids "cleverness" in __autoload() which can lead to unnecessary stat() calls, and it has the advantage that I can easily move files around within my code base, knowing that all I need to do is run a single script to update the autoloader.
The script itself recursively inspects my includes/ directory, and assumes that any PHP file not named in a short list of exclusions (the autoloader itself, plus some other standard files I tend to have) contains a class of the same name.
Zend Framework's approach is to do autoload based on the PEAR folder standard (Class_Foo maps to /Class/Foo.php), however rather than using a set base path it uses the include_path.
The problem with their approach is there's no way to check beforehand if a file exists so the autoload will try to include a file that doesn't exist in any of the include_path's, error out, and never give any other autoload functions registered with spl_autoload_register a chance to include the file.
So a slight deviation is to manually provide an array of base paths where the autoload can expect to find classes setup in the PEAR fashion and just loop over the base paths:
<?php
//...
foreach( $paths as $path )
{
if( file_exists($path . $classNameToFilePath) )
include $path . $classNameToFilePath;
}
//...
?>
Granted you'll kinda be search but for each autoload you'll only be doing at worst n searches, where n is the number of base paths you are checking.
But if you find yourself still having to recursively scan directories the question is not "Will autoload hurt my performance," the question should be "why am I tossing my class files around in a random structure?" Sticking to the PEAR structure will save you so many headaches, and even if you decide to go with manually doing your includes as opposed to autoload, there will be no guessing as to where the class files are located when you do your include statements.