so i've started using namespaces and read some docs but I seem to be doing something wrong.
First off is my application structure which is build like this:
root
-dashboard(this is where i want to use the autoloader)
-index.php
--config(includes the autoloader)
--WePack(package)
---src(includes all my classes)
now in the src directory I included the classes with:
namespace WePack\src;
class Someclass(){
}
the content of config.php is:
<?php
// Start de sessie
ob_start();
session_start();
// Locate application path
define('ROOT', dirname(dirname(__FILE__)));
set_include_path(ROOT);
spl_autoload_extensions(".php"); // comma-separated list
spl_autoload_register();
echo get_include_path();
and I use it like this in my index.php
require_once ('config/config.php');
use WePack\src;
$someclass = new Someclass;
this is what the echo get_include_path(); returns:
/home/wepack/public_html/dashboard
which is what I want I guess. but the classes are not loaded and nothing is happening. I'm obviously missing something but I can't seem to figure it out. could you guys take a look at it and explain to me why this isn't working?
The problem here is, that you don't register a callback function with spl_autoload_register(). have a look at the official docs.
To be more flexible, you can write your own class to register and autoload classes like this:
class Autoloader
{
private $baseDir = null;
private function __construct($baseDir = null)
{
if ($baseDir === null) {
$this->baseDir = dirname(__FILE__);
} else {
$this->baseDir = rtrim($baseDir, '');
}
}
public static function register($baseDir = null)
{
//create an instance of the autoloader
$loader = new self($baseDir);
//register your own autoloader, which is contained in this class
spl_autoload_register(array($loader, 'autoload'));
return $loader;
}
private function autoload($class)
{
if ($class[0] === '\\') {
$class = substr($class, 1);
}
//if you want you can check if the autoloader is responsible for a specific namespace
if (strpos($class, 'yourNameSpace') !== 0) {
return;
}
//replace backslashes from the namespace with a normal directory separator
$file = sprintf('%s/%s.php', $this->baseDir, str_replace('\\', DIRECTORY_SEPARATOR, $class));
//include your file
if (is_file($file)) {
require_once($file);
}
}
}
after this you'll register your autoloader like this:
Autoloader::register("/your/path/to/your/libraries");
Isn't this what you mean:
spl_autoload_register(function( $class ) {
include_once ROOT.'/classes/'.$class.'.php';
});
That way you can just call a class like:
$user = new User(); // And loads it from "ROOT"/classes/User.php
Related
I have an autoloader that is placed as a php file above all other sub directories in my project.
What it does is it loads all possible classes at once for any specific server request. After further thought I concluded I need to autoload only the required classes.
What do I need to do to avoid loading other classes not needed?
If I need to post the relevant code snippets of the class files in my subdirectories, I can.
<?php
namespace autoloader;
class autoloader
{
private $directoryName;
public function __construct($directoryName)
{
$this->directoryName = $directoryName;
}
public function autoload()
{
foreach (glob("{$this->directoryName}/*.class.php") as $filename)
{
include_once $filename;
}
foreach (glob("{$this->directoryName}/*.php") as $filename)
{
include_once $filename;
}
}
}
# nullify any existing autoloads
spl_autoload_register(null, false);
# instantiate the autoloader object
$classes = [
new autoloader('request'),
new autoloader('config'),
new autoloader('controllers'),
new autoloader('models'),
new autoloader('data')
];
# register the loader functions
foreach ($classes as $class)
spl_autoload_register(array($class, 'autoload'));
All registered autoloader functions will be called when you try to instantiate a new class or until it finally loads the class or throws an error. The way you have it now, you're registering the same autoloader function again and again for each directory, and file.
What you'd want to do is something along the lines of this.
namespace autoloader;
class autoloader
{
public function __construct()
{
spl_autoload_register([$this, 'autoload']);
}
public function autoload($classname)
{
if (! file_exists("{$classname}.class.php")) {
return;
}
include_once "{$classname}.class.php";
}
}
new autoloader();
Every autoloader function gets the class FQCN passed into it, and from there you'll have to parse it and figure out if you can load the file where that class exists. For instance, if I do the following.
use Some\Awesome\ClassFile;
$class = new ClassFile();
The autoloader we've registered will get the string Some\Awesome\ClassFile passed in as an argument, which we can then parse and see if we have a file for that class, if we don't we return out of the function and let the next registered autoloader function try and find the class.
You can read more about autoloaders in the documentation, I also wrote a blog post about it like 2 months ago that might interest you.
I had to refactor the code and remove the unnecessary load all functionality that I mistakenly thought would lazy load my classes on request.
Here is what I came up with:
Entry Point
<?php
require_once 'enums.php';
require_once 'api.class.php';
spl_autoload('AutoLoader\AutoLoader');
use App\API;
class MyAPI extends API
{
public function __construct($request){
parent::__construct($request);
}
}
$api = new MyAPI($_REQUEST);
echo $api->processRequest();
AutoLoader Implementation (located under subdirectory Autoloader/Autoloader.php and inaccessible via browser by using .htaccess)
<?php
namespace Autoloader;
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
spl_autoload_register("AutoLoader\AutoLoader::RequestLoader");
class Autoloader
{
public static function ClassLoader(String $fileName)
{
foreach ([".Class.php", ".php"] as $extension)
if (file_exists($fileName.$extension))
include $fileName.$extension;
}
public static function RequestLoader()
{
self::ClassLoader('Request');
}
}
Snippet for processRequest() (located in api.class.php - my request router)
public function processRequest()
{
$id1 = $this->requestObj->id1;
$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$goto = $this->requestObj->goto;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("No Endpoint: " . $endpoint2 ?? $endpoint));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "\\Controllers\\$className";
if (class_exists($class))
{
$method = strtolower($this->method);
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}();
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}
I am learning about and implementing PHP namespaces.
Please refer to the following code:
<?php
namespace Xicor\core;
class App {
public function __construct() {
$registry = Registry::getInstance();
//TODO: init router
//TODO: call controller#action
$controllerName = 'Xicor\app\controllers\\'.'Index';
$action = 'show';
$controller = new $controllerName();
$controller->$action();
}
}
The above code works perfectly.
If I add throw new Exception('Lorem Ipsum') within the constructor, I'll get an error as expected. To make it work, I must use throw new \Exception('Lorem Ipsum') so that we are referring to the global namespace.
But, why does
$controllerName = 'Xicor\app\controllers\\'.'Index'; successfully import the right class.
Why do I not have to use
$controllerName = '\Xicor\app\controllers\\'.'Index'; (with \ prefixed)?
If it affects anything, here's my autoloader:
<?php
spl_autoload_register(function($name) {
//replace \ with DIRECTORY_SEPARATOR
$path = str_replace('\\', DS, $name);
//replace Xicor with root
$path = str_replace('Xicor', __DIR__, $path); // __DIR__ doesn't return a trailing slash
//add .php at end
$path .= '.php';
if(file_exists($path)) {
require_once($path);
}
});
As I understand it. PHP will work in the current namespace within a class unless specified otherwise (a preceding \).
Example
namespace Bar;
class Foo {
function __construct()
{
// references current namespace, looks for Bar\Baz;
$baz = new Baz();
}
}
class Baz {
function __construct()
{
try {
// do stuff
// references global namespace
} catch(\Exception $e) {
var_dump($e->getMessage());
}
}
function foo() {
// This will prepend the current namespace to the class, in actual fact it will first look for "Bar\Bar\Foo"
// When it isnt found, it will move on and look at spl_autoload_register to try to resolve this class,
// Failing that you will get a ClassNotFoundException
$foo = new Bar\Foo();
}
}
Please see. https://www.php.net/manual/en/language.namespaces.rules.php
and https://www.php.net/manual/en/language.namespaces.faq.php#language.namespaces.faq.full for reference
I encounterd a little problem with my classes : they simply do not load through my autoloader.
I get this message error :
Warning:
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php):
failed to open stream: No such file or directory in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Fatal error: require(): Failed opening required
'C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php'
(include_path='.;C:\php\pear') in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Factory class :
use Core\config;
use Core\Database\MysqlDatabase;
class App {
public $title = "My super site";
private $db_instance;
private static $_instance;
public static function getInstance()
{
if (is_null(self::$_instance))
{
self::$_instance = new App();
}
return self::$_instance;
}
public static function load()
{
session_start();
require ROOT . '/app/Autoloader.php';
App\Autoloader::register();
require ROOT .'/core/Autoloader.php';
Core\Autoloader::register();
}
public function getTable($name)
{
$class_name = '\\App\\Table\\' . ucfirst($name) .'Table';
return new $class_name($this->getDb());
}
public function getDb()
{
$config = Config::getInstance(ROOT . '/config/config.php');
if (is_null($this->db_instance)) {
$this->db_instance = new MysqlDatabase($config->get('db_name'), $config->get('db_user'), $config->get('db_pass'), $config->get('db_host'));
}
return $this->db_instance;
}
}
Namespace App autoloader class :
<?php
namespace App;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Namespace Core autoloader class :
<?php
namespace Core;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Empty PostTable
namespace App\Table;
use Core\Table\Table;
class PostsTable extends Table
{
}
Index page :
define('ROOT', dirname(__DIR__));
require ROOT . '/app/App.php';
App::load();
$app = App::getInstance();
$posts = $app->getTable('Posts');
var_dump($posts->all());
How to make it works please?
AS I said in the comments check this path
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php)
Doesn't look right to me
require(C:\wamp64\www\blog\ [appAutoloader.php] /Table/PostsTable.php)
What's that bit doing there....
Also namespace of App is not app for the folder its App because this may work on Windows but you will find it does not work on Linux. Because Linux paths are case sensitive, and windows are not.
Further this makes little to no sense
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
Require 2 files? Paths don't work that way, not that I am aware of at least.
On top of that your implementation ignores the _ Typically underlines will be part of the class name but are replaced by directory, this allows a shorter namespace. So for example instead of having a namespace like this
Namespace \APP\Table;
class PostsTable ..
You could have a class in the same place Like so
Namespace \APP;
class Table_PostsTable ..
With a shorter namespace but still located in the App/Table/PostsTable.php file. However, that's just how I read the spec for PSR autoloaders.
PRO TIP
Take this path C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php open the file browser on you desktop and see if it pulls up the file by pasting it into the navigation bar. It wont, but you can be sure your path is wrong by eliminating the code.
I wrote an Autoload:
<?php
function autoload($class)
{
try
{
global $path;
$elements = explode("\\", $class);
$name_class = end($elements);
foreach ($elements as $element)
{
if($element != $name_class)
{
$path .= $element."/";
echo "path:".$path."<br/>";
}
}
$file = strtolower(($path).$name_class.".php");
if(file_exists($file))
{
include($file);
return;
}
throw new Exception($class." not founded");
}
catch(Exception $e)
{
echo $e->getMessage();
return;
}
}
class Autoloader
{
public static function autoload($class)
{
autoload($class);
}
}
spl_autoload_register('autoload');
spl_autoload_register(array('autoloader', 'autoload'));
?>
The Autoload finds a relative path and change it in an absolute path.
Now I have problem when I call a class from another file with different namespace.
For example if I have this file Base.php in the directorynamed framework:
namespace Framework;
use Framework\Inspector as Inspector;
class Base
{
private $_inspector;
public function __construct($options = array())
{
$this->_inspector = new Inspector($this);
...
Now I have the file Inspector.php in the same directory with the same namespace:
<?php
namespace Framework;
use Framework\ArrayMethods as ArrayMethods;
use Framework\StringMethods as StringMethods;
use \Exception as Exception;
class Inspector
{
...
?>
When I try to instantiate the class Base, I get the exception message about missing Inspector class.
The same think if I try to extend Base class in other directories. I tried to add some echoes in my autoload function and it printed that he use local namespace of the child class or a class who uses an instance by composition of another class to create the string for finding the php file.
For example when I try the load Inspector in Base with echo I got:
path:Framework/
file:::C:\LightTPD\htdocs\mvc\framework\framework\Base.php (I add some ::: to make more readable)
framework/base.php
path:Framework/Framework/
file:::
framework/framework/inspector.php (trying to use the namespace of inspector after using the one of Base)
Framework\Inspector not foundedpath:Framework/Framework/Framework/
file:::
framework/framework/framework/inspector.php
Framework\Inspector not founded
When I tried to extend class Base by class Driver I got this:
path:Framework/
path:Framework/Configuration/
file:::C:\LightTPD\htdocs\mvc\framework\framework\configuration\Driver.php
framework/configuration/driver.php (everything ok)
path:Framework/Configuration/Framework/ (Framework is from Base; Configuration/Framework is from driver)
file:::
framework/configuration/framework/base.php
Framework\Base not foundedpath:Framework/Configuration/Framework/Framework/
file:::
framework/configuration/framework/framework/base.php
Framework\Base not founded
-###############################################################
The directory tree is:
It's:
-framework: ArrayMethods.php, Base.php,Configuration.php, Inspector.php, StingMethods.php
-configuration: Driver.php
-exception
-core
-exception
How can I fix it?
Sorry I noticed that the problem was the global $path.
Now I wrote the autoload.php in this way:
<?php
function autoload($class)
{
try
{ $path = NULL;
$elements = explode("\\", $class);
$name_class = end($elements);
foreach ($elements as $element)
{
if($element != $name_class)
{
$path .= $element."/";
$file = realpath(strtolower(($path).$name_class.".php")); //realpath is optional
if(file_exists($file))
{
include($file);
//echo $file."<br><br>"; testing in for debugging
return;
}
}
}
throw new Exception($class." not founded");
}
catch(Exception $e)
{
echo $e->getMessage();
return;
}
}
class Autoloader
{
public static function autoload($class)
{
autoload($class);
}
}
spl_autoload_register('autoload');
spl_autoload_register(array('autoloader', 'autoload'));
?>
Now even if it's less perfomant everything it's ok, he work whatever a class call another, it's based only the namespace of a class.
Make sure you include the files path of the class you want to extend(parent class) from to your child class file before the use namespaces of the parent/Base class.
Trying to understand how namespaces and autoload works on PHP
Server.php located at core/server.php
namespace core\server
{
class Main
{
public function getTopic()
{
$get_params = $_GET;
if (empty($get_params)) $get_params = ['subtopic' => 'test'];
return $get_params;
}
}
}
and Index.php
spl_autoload_register();
use core\server as subtopic;
$test = new subtopic\Main();
var_dump($test);
It cant load the class core/server/Main
Autoload doesn't work that way.
First I will explain how autoloaders works.
spl_autoload_register() is a function to register a function you have in your code to server as an autoloader, the standard function would be:
define('APP_PATH','/path/to/your/dir');
function auto_load($class)
{
if(file_exists(APP_PATH.$class.'.php'))
{
include_once APP_PATH.$class.'.php';
}
}
spl_autoload_register('auto_load');
The constant APP_PATH would be the path to your directory where your code lives.
As you noticed it the param that is passed to spl_autoload_register is the name of my function, this register the function so when a class is instanciated it runs that function.
Now an effective way to use autoloaders and namespaces would be the following:
file - /autoloader.php
define('APP_PATH','/path/to/your/dir');
define('DS', DIRECTORY_SEPARATOR);
function auto_load($class)
{
$class = str_replace('\\', DS, $class);
if(file_exists(APP_PATH.$class.'.php'))
{
include_once APP_PATH.$class.'.php';
}
}
spl_autoload_register('auto_load');
file - /index.php
include 'autoloader.php';
$tree = new Libs\Tree();
$apple_tree = new Libs\Tree\AppleTree();
file - /Libs/Tree.php
namespace Libs;
class Tree
{
public function __construct()
{
echo 'Builded '.__CLASS__;
}
}
file - /Libs/Tree/AppleTree.php
namespace Libs\Tree;
class AppleTree
{
public function __construct()
{
echo 'Builded '.__CLASS__;
}
}
I'm using namespaces and autoload to load my functions in a nicely way, you can use namespace to describe in what dir your class resides and uses the autoloader magic to load it without any problems.
Note: I used the constant 'DS', because in *nix it uses '/' and in Windows it uses '\', with DIRECTORY_SEPARATOR we don't have to worry where the code is going to run, because it will be "path-compatible"