PHP __autoload() with namespace - php

spl_autoload_register('Think\Think::autoload');
Under namespace Think\ I created the above register function,when I try to use a class that has not been included like class Storeage,php will surposely pass Storeage as the variable to function Think\Think::autoload,but it actually passed Think\Storeage as the variable,why it adds the extra Think\ to the autoload instead of just Storeage?
Does that mean autoload will only search for classes which are declared under the same namespace where the autoload function is created?

Autoload functions generally work by including files for you on demand. So, for instance, I have a class called Spell in the namespace Write and it's in write/spell.php. So I tell my autoload function how to find the file (in this case, my directories mirror my namespacing).
The autoload function itself doesn't care about namespaces per se. It cares about finding the files that contain your class and loading them. So, to answer your question, your autoload will only restrict itself to a namespace if you write the function to do that.
Now, here's the caveat with the way you're doing it. Your autoload function is already in a namespace. That means you will have to manually include the file that contains that class or else your autoload will fail.

Here is an example for you .
loader.php
namespace bigpaulie\loader;
class Loader {
/**
* DIRECTORY_SEPARATOR constatnt is predefined in PHP
* and it's different for each OS
* Windows : \
* Linux : /
*/
public static function load($namespace){
$filename = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . ".php";
if(file_exists($filename)){
require_once $filename;
}else{
throw new \Exception("Error Processing Request", 1);
}
}
}
index.php
require_once 'path/to/loader.php';
spl_autoload_register(__NAMESPACE__ . 'bigpaulie\loader\Loader::load');
$class1 = new \demos\Class1();
// or
use bigpaulie\core\Class2;
$class2 = new Class2();
as you can see we can use whatever namespace needed we just have to make sure that the path to the class file exists .
Hope this helps!
Best regards,
Paul.

Related

How to make sure the correct class file is loaded when calling / extending class

I did brows through that question page but the answers given, I felt, didn't sufficiently answer the question I was struggling with.
I have a little php project. In this project I define two classes both in their own files. The second class extends the first class. In the app.php file I instantiate the second class and call a method.
├── Models/
│ ├── Class1.php
│ └── Class2.php
└── app.php
<?php
// Class1.php
namespace Models\Class1;
/**
* Class1 does things
*/
class Class1 {
public function someMethod() {
// code
}
}
<?php
// Class2.php
namespace Models\Class2;
use \Models\Class1\Class1;
include('./Class1.php');
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
<?php
// app.php
use Models\Class1\Class1;
use Models\Class2\Class2;
require('Models/Class1.php');
require('Models/Class2.php');
$c1 = new Class1();
$c2 = new Class2();
Now I'm not a really experienced php programmer but I thought I had a solid grasp of this class/namespace business and including/requiring files, but apparently I don't.
If I copy the methods I need from Class1 to Class2 and not extend/include/use Class2 everything works fine, but want to extend Class1, so I don't have to repeat myself! Damn it!
This is what I get when running the app.php file.
Warning: include(../Models/Class1.php): failed to open stream: No such file or directory in C:\wamp64\www\project\Models\Class2.php on line 8
Warning: include(): Failed opening '../Models/Class1.php' for inclusion (include_path='.;C:\php\pear') in C:\wamp64\www\project\Models\Class2.php on line 7
I've been reading all I can find online about this stuff, have written and rewritten my code in a dozen different ways but have yet to find a solution. Some help would be greatly appreciated!
Quick Answer
Unless you've specified the full path to the include file, PHP will always try to resolve files according to the location of the entry script. In your case, your entry script seems to be app.php at the root of the application.
So in your Class2.php, instead of include('./Class1.php');, you should write include('Models/Class1.php'); and things should work. But while it fix a short term problem, your code would be really non-portable. Besides, including the same file twice would result in another error (e.g. re-declaring class).
Slightly Smarter Approach
A slightly smarter would be to do this in Class2.php instead.
<?php
// Class2.php
namespace Models\Class2;
use \Models\Class1\Class1;
include_once __DIR__ . '/Class2.php';
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
The variable __DIR__ will always be resolved to the directory of the script file, not the entry script.
But again, it is clumsy and error prone to do file includes by hand.
An Even Smarter Approach: Autoload
PHP supports file autoloading when a class is declare. That is to have a piece of code to do the file include when a class is called. If you want to have fun, you're welcome to write your own autoloader function and register to spl_autoload_register. With a combination of the __DIR__ technique we talked about, you can easily resolve the autoloading path from namespace.
A quick and ugly autoloading app.php would probably look like this:
<?php
// app.php
use Models\Class1\Class1;
use Models\Class2\Class2;
spl_autoload_register(function ($class_name) {
$realClassName = basename(str_replace('\\', DIRECTORY_SEPARATOR, $class_name));
include_once __DIR__ . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR . $realClassName . '.php';
});
$c1 = new Class1();
$c2 = new Class2();
The Smart Approach: Composer Autoload
If you're learning OOP PHP today, I'd highly recommend you to learn Composer and PSR-4. PSR-4 defines how you should structure a PHP application for class autoloading. Composer implements a PSR-4 autoloader by default.
First you should comply with the namespace standard. The least change to do is to loose the extra "Class1" and "Class2" in your namespace:
<?php
// Class1.php
namespace Models;
/**
* Class1 does things
*/
class Class1 {
public function someMethod() {
// code
}
}
<?php
// Class2.php
namespace Models;
use \Models\Class1;
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
With a nicely structured application folder, namespace structure and a correctly written autoload.psr-4 section in composer.json, composer will help you to generate a class autoloader. Your composer.json would probably look like this:
{
"autoload": {
"psr-4": {
"Model\\": "Model/"
}
}
}
You may now run composer dump-autoload to create the autoloader. Then you can simply add this to your entry script app.php. When things are ready, you may simply add use and new statement anywhere in the application.:
<?php
// app.php
use Models\Class1;
use Models\Class2;
require_once './vendor/autoload.php';
$c1 = new Class1();
$c2 = new Class2();
All includes are handled by the autoloader. Hands free.
Remove the line
include('./Class1.php');

Class found in PhpStorm but not when running

I'm using PhpStorm to develop an iCal Manager.
I have a class User defined in the namespace Engine used for all the website, and a class User defined in the namespace iCal used for iCal process only, extended from \Engine\User.
When coding, PhpStorm can locate \Engine\User in order to define \iCal\User, but when executing I got a "Fatal Error: Class \Engine\User not found".
I'm using PhpStorm 2019.1 with PHP 7.2.4 on WampServer.
I tried to make an alias, didn't work. I tried to require all my files in one then to add it in 'Engine', didn't work either.
\Engine\User:
<?php
declare(strict_types=1);
//Created for IGPD by leroy the 27/07/2019
namespace Engine;
use PDO;
use Exception;
use PDOStatement;
/**
* Class User
* #property bool attendee
* #package Engine
*/
class User
{
public function __construct(array $user)
{
$this->email = $user['email'];
$this->name = $user['name'];
$this->surname = $user['surname'];
$this->language = $user['language'];
}
}
\iCal\User:
<?php
declare(strict_types=1);
//Created for IGPD by leroy the 27/07/2019
namespace iCal;
use Engine\User as UserAlias;
/**
* Class User
* #package iCal
*/
class User extends UserAlias
{
public function __construct(array $user, string $cutype, string $role, bool $rsvp, string $partstat, string $sentby, string $dir, string $type)
{
$this->calAddress = "mailto:" . $user['email'];
$this->cutype = in_array($cutype, ['INDIVIDUAL', 'GROUP', 'RESOURCE', 'ROOM', 'UNKNOWN']) ? $cutype : 'UNKNOWN';
$this->role = in_array($role, ['CHAIR', 'REQ-PARTICIPANT', 'OPT-PARTICIPANT', 'NON-PARTICIPANT']) ? $role : 'OPT-PARTICIPANT';
$this->rsvp = $rsvp ? 'TRUE' : 'FALSE';
$this->cn = $user['name'] . " " . $user['surname'];
$this->language = in_array($user['language'], ['EN', 'FR']) ? $user['language'] : 'EN';
$this->partstat = in_array($partstat, ['NEEDS-ACTION', 'ACCEPTED', 'DECLINED', 'TENTATIVE', 'DELEGATED']) ? $partstat : 'NEEDS-ACTION';
$this->sentby = $sentby;
$this->dir = $dir;
$this->type = $type;
UserAlias::__construct($user);
}}
I just run the class file to check for errors, so I should not get anything.
The actual output is:
Fatal error: Class 'Engine\User' not found in D:\wamp\www\IGPD\iCal\User.php on line 12
Here is iCal.php, the file that require all the files in the directory:
\iCal\iCal.php:
<?php
declare(strict_types=1);
namespace iCal;
//Created for IGPD by leroy the 02/08/2019
require_once 'Alarm.php';
require_once 'Calendar.php';
require_once 'Event.php';
require_once 'FreeBusy.php';
require_once 'Journal.php';
require_once 'Todo.php';
require_once 'User.php';
Then I used this in my main page but it's not the one I want to load so I shouldn't need it:
\Engine\Engine.php:
<?php
declare(strict_types = 1);
namespace Engine;
session_start();
require_once "collections.php";
require_once "..\iCal\iCal.php";
I do not use autoload.
I've explored the autoload solution mentioned by #ÁlvaroGonzález, and so far it's the only working solution. As a result, I came up with this code, in the index.php at the root of my directory.
\index.php:
<?php
declare(strict_types=1);
namespace iCal;
spl_autoload_register(function ($class){
require_once ucfirst($class) . '.php';
});
$e = new Calendar("e", "e", "e", "r", "e");
$f = new User(['email' => "", 'name'=>"", 'surname'=>"", 'language'=>""], "e", "e", true, "e", "e", "e", "e");
I'm glad to say it worked, but I'm afraid that is not the best solution, mainly because this code don't work if the class is defined in another directory.
When you run php foo.php the PHP interpreter loads that single file and no other, unless you explicitly instruct it to do otherwise. On the other side, your IDE does not need to run your code. It just scans your project's directory, compiles all classes, functions, constants, etc. and assumes they're available when you ask about them.
Any complex program calls for splitting code in several files, thus you need to address it. Almost all solutions available involve include et al. in some way, but the details can differ vastly. It's also important to note that namespaces do not play any role in class or file loading—it's just a mechanism to have more short names available as codebases grow and third-party libraries thrive.
Your are using direct calls to require_once to load files. That's roughly equivalent to copying and pasting the included files's code in the exact location of the require_once construct, but there's a caveat: PHP parses each file independently and if you're loading a file with a class definition that depends on another class, you need to have that other classed parsed earlier. Look at this example:
<?php // Animal.php
class Animal
{
}
<?php // Cat.php
class Cat extends Animal
{
}
This script runs flawlessly:
<?php
require_once 'Animal.php';
require_once 'Cat.php';
This one triggers Fatal error: Uncaught Error: Class 'Animal' not found in C:\tmp\Cat.php:2:
<?php
require_once 'Cat.php';
require_once 'Animal.php';
So this is your exact problem: you aren't loading your files in the correct order.
This can be tricky in relatively small projects and it doesn't scale well so that's why class auto-loading was implemented. PHP provides a hook function you can customise and calls that function whenever it finds code that needs a yet unknown class. Your custom function gets the class name as argument and does whatever it sees fit (typically involving an include call).
Some guys wrote a spec on how an autoloader could work and called it PSR-4. The well-known Composer package manager wrote an auto-loader implementing that spec. So, if you use Composer in your projects, you can benefit from that auto-loader without the need of writing any code. All you need to do is to follow a specific naming convention and directory structure and define where your classes root directory is. So, if you want a class called \Engine\User you define it in a file located at /Blah/Blah/Engine/User.php and the class is loaded automatically when it's used in your code.

Class extend in php assistance

I have a autoload function that looks like so:
function __autoload_HTTP_Client($class_name) {
$HC = $class_name . '.class.php';
return require_once($HC);
}
when i call a class i use the $users = new users();
What i want to do is place my class files in folders within my class directory. So i will have
/users/users.class.php
/general/general.class.php
In my users class i want to be able to extend general.class.php
class users extends general {
}
because I want to be able to use $this-> from general so i dont want to use include. but when i do extend general it says that the class is not found since its in a different directory.
Any ideas on fixing this (I know the autoload needs to change to use directory/ $file.class.php
but how can i extend a class that is outside of the directory of the current class.
Use:
set_include_path(get_include_path().':/users:/general');
The above will then look for files within those directories, and you don't have to use a path, just filename, etc on the include/require inside the autoloader.

Autoload .obj.php files in Zend?

I am using a custom scheme in naming my files. Any pointers on how to autoload them with Zend's autoloader?
Thanks!
You can write your own autoloader class and register it with the ZF autoloader. There is a brief bit about this in the manual: http://framework.zend.com/manual/en/zend.loader.autoloader.html#zend.loader.autoloader.interface
Basically it's just a class defining one method which takes the class name as the parameter. So for you it would be something like:
class My_Autoloader implements Zend_Loader_Autoloader_Interface
{
public function autoload($class)
{
$filename = $class.'.obj.php';
require $filename;
}
}
the above assumes the classes are on the include path, if not, just stick the full path in front of $filename. Add any other mapping/checking you need in here and then register it as per the example in the manual. Normally you would do this in your bootstrap.

Will a directory with a period break autoload resolution based on a namespace in Zend Framework?

I have a folder in my library folder which is named after my website. The folder path is like:
~\www\library\myWebsite.com
If I'm using Zend autoloader to load the namespace of everything in the library path, will I have any trouble autoloading a class from that file with a namespace like this:
\myWebsite.com\myClass::myFunction();
I have looked online for documentation on this and I can't find any info about using periods in this way.
I tried it and the complication is in PHP. I think Zend is registering the namespace fine, because when I call \Zend_Load_Autoloader::getRegisteredNamespaces() it shows that it's registered. but when I call the static method from the fully qualified namespace, php gives an error of this:
Fatal error: Undefined constant 'myWebsite' in /home/jesse/www/application/controllers/MyController.php on line 15
It seems like PHP is terminating the namespace identifier, during parsing, at the . (period character). This is dissapointing because to me having a library named after the website was important to my design.
I will rename the directory to myWebsitecom or possibly make the .com it's own sub directory like
myWebsite\com and incorporate that into my namespace tree like: \MyNamespace\Com\MyClass::myFunction();
The easiest way to find out is to try it.
If it doesn't work, you could always write a custom autoloader to make it work. I don't have much experience with php namespaces, but the autoloader would look something like this (I imagine you'll have to tinker with it a bit to determine the correct file path given the class name):
<?php
class My_Loader_Autoloader_MyWebsite implements Zend_Loader_Autoloader_Interface {
/**
* (non-PHPdoc)
* #see Zend_Loader_Autoloader_Interface::autoload()
*/
public function autoload($class) {
if (strtolower(substr($class, 0, 9)) == 'mywebsite') {
$file = realpath(APPLICATION_PATH . '/../library/myWebsite.com/' . $class);
if ($file) {
require_once $file;
return $class;
}
}
return false;
}
}
then put this in your bootstrap:
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->pushAutoloader(new My_Loader_Autoloader_MyWebsite());
and if this class must be in that myWebsite.com directory, you could just cheat and throw in a require in there too:
require_once(APPLICATION_PATH . '/../library/myWebsite.com/Loader/Autoloader/MyWebsite.php');

Categories