I am creating a website and I am using Apache as my webserver.
I have created a website without the use of classes. All of my functions are located in one file and I just include this file on every page where I need to use some of the functions.
I am now wanting to swap to an OOP approach but I am having trouble understanding how to autoload my classes. I have been through a number of related pages, such as; PSR-4 Example, spl_autoload_register(), Related Question and I just can't seem to get my head around this.
So, as I am using Apache, the path for the root of my website is C:\Apache\htdocs.
My directories look as follows;
+ htdocs
+ Lib
+ Base
- User.php
- Device.php
- Company.php
- Database.php
+ Services
- UserService.php
- DeviceService.php
- CompanyServer.php
+ Config
- DbConfig.php
As an example so you can help me get my head around this, we will just talk about two classes DbConfig and Database.
DbConfig.php (with connection details omitted)
<?PHP
namespace Lib\Config;
class DbConfig
{
protected $serverName;
protected $userName;
protected $password;
protected $dbName;
public function __construct()
{
$this->serverName = 'not my server name';
$this->userName = 'not my username';
$this->passCode = 'not my password';
$this->dbName = 'not my database';
}
}
Database.php (Very simple until I can actually use it - at which point I will add its functionality)
<?PHP
namespace Lib\Base;
use Lib\Config\DbConfig;
class Database extends DbConfig
{
protected $connection;
private $dataSet;
private $sqlQuery;
public function __construct()
{
parent::__construct();
$this->connection = null;
$this->dataSet = null;
$this->sqlQuery = null;
}
}
So..
How do I load these classes, using some implementation of spl_autoload_register(), into another .php file so that I may use them to create objects? for example in a file named 'Testing.php'
Previously I would parse a .ini file outside of the root of my folder to get the database connection details, should I do this with my new approach of having the connection details in the class DbConfig?
According to the PSR-4 standard,
The fully qualified class name MUST have a top-level namespace name, also known as a "vendor namespace".
Your classes don't seem to have this. To add it, change namespace Lib\Config; to, for example, namespace AskMeOnce\Lib\Config;. You'll need to add this prefix to all your classes.
Then, using the PSR-4 Autoloader you referenced, change $prefix = 'Foo\\Bar\\'; to $prefix = 'AskMeOnce\\'; and $base_dir = __DIR__ . '/src/'; to $base_dir = 'C:\Apache\htdocs';. Put that function in a file called autoload.php, and require it in any file that needs to know how to autoload.
Once you require that autoloader, PHP will know to look for any class that begins with the namespace AskMeOnce in the specified base directory. After removing that prefix from the namespace, the namespace represents the rest of the path. For example, AskMeOnce\Lib\Config\DbConfig Will be looked for at <basedir>/Lib/Config/DbConfig.php.
To answer your questions specifically, (1) Just put the function in autoload.php, with the modifications I mentioned above. And (2) since the ini file is not a PHP class, it doesn't matter where it is as long as your code knows how to find it (whether this is a hard-coded path or otherwise).
Here is a quick example I knocked together:
Create a file called autoloader.php. This example was taken from PSR-4 Examples. Change Acme to your projects name.
This will allow you to use namespaces, so long as they are stored under Lib/ and you add the namespaces to each file (as you're already doing).
<?php
/**
* An example of a project-specific implementation.
* #param string $class The fully-qualified class name.
* #return void
*/
spl_autoload_register(function ($class) {
// project-specific namespace prefix
$prefix = 'Acme\\';
// base directory for the namespace prefix
$base_dir = __DIR__ . '/Lib/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
print $file;
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
});
This uses spl_autoload_register to handle autoloading of dependencies/classes, and also allows for the use of PRS-4 namespaces.
Next, your Database class would be stored in Lib/Base/Database.php.
<?php
namespace Acme\Base;
class Database
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function allUsers()
{
/*
* Query to fetch all users from the database
*/
return [
[
'username' => 'Bob',
'role' => 'member'
],
[
'username' => 'Joe',
'role' => 'admin'
]
];
}
}
And finally, your index page includes the autoloader.php script, which takes care of automatically included the classes.
<?php
require_once 'autoloader.php';
$config = require 'config.php';
try {
$dbc = new PDO("mysql:dbname={$config['MYSQL']['DATABASE']};host={$config['MYSQL']['HOST']}",
$config['MYSQL']['USERNAME'], $config['MYSQL']['PASSWORD']);
} catch (PDOException $e) {
die('Could not connect to database ' . $e->getMessage());
}
$db = new \Acme\Database($dbc);
print_r($db->allUsers());
And finally, you asked about configuration files. I use a config file like so:
<?php
return [
'MYSQL' => [
'USERNAME' => 'root',
'PASSWORD' => '',
'DATABASE' => 'test',
'HOST' => 'localhost'
]
];
Which allows you to include the config file with a simple:
$config = require 'config.php';
And access like so:
$config['MYSQL']['USERNAME'];
I pass the \PDO database connection as a dependency into the Database class as an example. This is called Dependency Injection.
Another example: Lib/Services/UserService.php:
<?php
namespace Acme\Services;
class UserService
{
...
}
You can now call this in code (provided the autoloader is included) like so:
$userService = new \Acme\Services\UserService;
I'd also recommend checking out Composer. Super helpful if you want to use public packages from Packagist and it's very easy to create your own. Supports PSR-* autoloading too (PSR4 being most common).
register a autoload function:
add the code in any file could be included before you use other php library classes, for example in your project entry file.
defined('ROOT') or define('ROOT', 'C:/Apache/htdocs');
// define root directory
// or `defined('ROOT') or define('ROOT', __DIR__)` if
// the file is in the root directory
spl_autoload_register(function($name) {
$file = ROOT . "/{$name}.php";
if(!is_file($file)) {
return false;
} else {
return include $file;
}
}, false, true);
not recommend to use namespace if your project is not big enough, just use directory and file name to do it
Related
As the title states; how can I use the HTMLPurifier library inside my class?
I'm trying to get to grips with OOP and PHP classes for the first time and have successfully built a class that connects to my database using my database class and returns a blog article.
I would now like to parse the HTML markup for the blog article using HTMLPurifier but I would like to achieve this inside my blog class and I'm wondering how it can be achieved, as HTMLPurifier is a class.
My class so far:
namespace Blog\Reader;
use PDO;
use HTMLPurifier_Config; <--- trying to include it here
use \Database\Connect;
class BlogReader {
private static $instance = null;
private static $article = null;
private static $config = null;
private static $db = null;
private static function InitDB() {
if (self::$db) return;
try {
$connect = Connect::getInstance(self::$config['database']);
self::$db = $connect->getConnection();
} catch (Throwable $t) {}
}
private function __construct($config) {
self::$config = $config;
}
public static function getInstance($config) {
if (!self::$instance) {
self::$instance = new BlogReader($config);
}
return self::$instance;
}
public static function getArticle($id) {
self::InitDB();
try {
if (self::$db) {
$q = self::$db->prepare("
// sql
");
$q->bindValue(':id', (int) $id, PDO::PARAM_INT);
$q->execute();
self::$article = $q->fetchAll(PDO::FETCH_ASSOC);
//////////// <----- and trying to use it here
$HTMLPurifier_Config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($HTMLPurifier_Config);
///////////
} else {
throw new Exception("No database connection found.");
self::$article = null;
}
} catch (Throwable $t) {
self::$article = null;
}
return self::$article;
}
private function __clone() {}
private function __sleep() {}
private function __wakeup() {}
}
However, I get the following error log when trying anything like this:
Uncaught Error: Class 'HTMLPurifier_Config' not found in
....../php/classes/blog/reader/blogreader.class.php
And the line number of the error is on this line:
$HTMLPurifier_Config = HTMLPurifier_Config::createDefault();
My class directory structure:
[root]
[blog]
blog.php <--- using classes here
[php]
afs-autoload.php
[classes]
[blog]
[database]
[vendor]
[htmlpurifier-4.10.0]
[library]
HTMLPurifier.auto.php <--- this is what I used to `include` on blog.php to autoload HTMLPurifier_Config::createDefault() and new HTMLPurifier($purifier_config).
My Autoloader (afs-autoload.php) file:
define('CLASS_ROOT', dirname(__FILE__));
spl_autoload_register(function ($class) {
$file = CLASS_ROOT . '/classes/' . str_replace('\\', '/', strtolower($class)) . '.class.php';
if (file_exists($file)) {
require $file;
}
});
I literally started learning classes today, so I'm really baffled as to how I can achieve this, especially with the namespace system I used.
I hope somebody with better experience can guide me in the right direction.
Rewritten answer:
1) Your auto loader is looking for <class>.class.php files; but your HTMLPurifier_Config is in a HTMLPurifier.auto.php file.
2) Still in your autoloader: str_replace('\\' You do not need to escape characters when in single quotes, so this should be: str_replace('\'.
3) This excellent answer should help you learn when and how to use the use PHP keyword.
4) Your issue is not the scope of your use (I don't think you even need to use use). But is that your autoloader is looking for the wrong type of files. Try manually loading the class using require and seeing if it works properly.
Original Answer
namespace Blog\Reader;
use PDO;
use HTMLPurifier_Config;
What you're actually doing here is using the values within the defined namespace; so you're using:
Blog\Reader\HTMLPurifier_Config
If you're HTMLPurifier_Config file is within its own namespace you need to specify that so that the "use" grabs the right data!
If its not in its own namespace then it will be in the global namespace which is identified with a slash:
namespace Blog\Reader;
use PDO;
use \HTMLPurifier_Config;
If it is in the namespace HTMLPurifier, for example:
namespace Blog\Reader;
use PDO;
use \HTMLPurifier\HTMLPurifier_Config;
to load the correct data reference.
Just to wrap this up, if you are using namespaces inside a class which as been placed in a namespace, this is how you create your purifier objects:
$config = \HTMLPurifier_Config::createDefault();
$purifier = new \HTMLPurifier($config);
$clean_html = $purifier->purify($dirty_html);
You do not have to use a use command since the HTMLPurifier classes themselves are not in a namespace. But when your code is in a namespace, you need to pre-pend '\' to non-namespaced classes.
namespace \Tdiscus; // This is the line that makes the difference.
use \Tsugi\Util\Net;
class Threads {
...
$config = \HTMLPurifier_Config::createDefault();
...
$retval = new \stdClass();
...
$dbh = new \PDO($dsn, $user, $password);
}
Because you placed the class in a namespace, any "non-namespaced" classes need a "" prefix - like PDO and stdClass in the above example.
The HTMLPurifier folks could have put their classes in a namespace - but they chose not to do that.
Using namespaces is a common practice when developing libraries intended for use with composer. But HTMLPurifier existed before composer was in common use and their classes have a nice unique prefix because they started out in a global class namespace - so they decided to leave well enough alone and not break the non-dynamic loading / non-namespace legacy adopters of their library.
I have a file which defines a name like this.
<root>/dir1/dir2/file1.php
//directory indicator
define("DI", "../../");
//adding required files
require_once DI . 'lib/Config.php';
require_once DI . 'lib/Common.php';
This adds Config.php and Common.php correctly.
Now when I try to get DI in Config.php like this
<root>/Config.php
<?php
class Config
{
public $value = DI . 'some_value';
}
I cant get that value there.
How can I make DI available inside Config class ?
EDIT
Real Folder Hierarchy
ROOT--> somefile.php
|__ lib --> config.php
|
|___ dir1
|
|__ dir2
|
|__ file1.php
I need to get root directory inside the class that is defined in config.php. that is I need to include somefile.php from config.php. I know I can do this simply like
include '../somefile.php';
But the problem is config.php holds a class which contains static methods. So I can get those methods like this.
Config::MethodInsideConfig();
Now when I try this from file1.php, it seems that ../somefile.php is trying to include from dir1. I think php uses file1.php's location for calculating the preceding directory. But what I need is it should get the file from root directory.
Inject the value as an argument into your class __construct(), and set the property in your constructor
class Config
{
public $value;
public __construct($di) {
$this->value = $di . 'some_value';
}
}
$myConfig = new Config(DI);
Why defining new constant PHP already has predefined constante DIR it's contain the current file directory.
So, your requires will be like this :
//adding required files
require_once __DIR__.'/../../'. '/lib/Config.php';
require_once __DIR__.'/../../'. 'lib/Common.php';
USING INI FILE
You can use config file (like config.ini)
[parameteres]
path_app_root="/usr/share/apache/www/my_app";
Then, your config as singleton class will be used to parse ini file and get config
<?php
class Config
{
public $configs = array();
private static $instance;
private function __construct() {}
public static function getInstance()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
self::$instance->configs = parse_ini_file(__DIR__."/../config.ini");
}
return self::$instance;
}
public function getRootPath()
{
return self::$instance->configs['path_app_root'];
}
public function __clone()
{
trigger_error('Clone is not allowed.', E_USER_ERROR);
}
}
Your file1.php will be like this
<?php
//adding required files
require_once '../../lib/Config.php';
echo Config::getInstance()->getRootPath();
Anas
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"
I have the following directory structure:
/var/www/Project1/Project1.php
/var/www/Project1/User/UserProfile.php
Inside Project1.php:
<?php
namespace Project1;
set_include_path( __DIR__ );
spl_autoload_extensions('.php');
spl_autoload_register();
use User\UserProfile;
$u = new Avatar();
...
?>
Inside UserProfile.php:
<?php
namespace Project1\User;
class Avatar{
}
...
?>
When I execute php Project1.php I get:
PHP Fatal error: spl_autoload9(): Class User\UserProfile could not be loaded
I don't see the problem.
spl_autoload_register(); when called with no params will just register the default autoloader which fails to handle namespaces with your project layout. You'll have to register your own method to make it work. Like this:
spl_autoload_register('my_autoload');
And here comes the autoload function. This function expects the classes to be stored in a way like:
/path/to/project/Namespace/Classname.php
/path/to/project/Namespace/Subnamespace/Classname.php
You can name the classes like \Namespaces\Classname or the old style way Namespace_Classname:
function my_autoload ($classname) {
// if the class where already loaded. should not happen
if (class_exists($classname)) {
return true;
}
// Works for PEAR style class names and namespaced class names
$path = str_replace(
array('_', '\\'),
'/',
$classname
) . '.php';
if (file_exists('/path/to/project/' . $tail)) {
include_once 'path/to/project/' . $tail;
return true;
}
return false;
}
Note that the function is taken from my github package Jm_Autoloader. The package provides more functionality as multiple include paths, path prefixes and static autoloading (with a predefined assoc array class name => file name). You can use it if you like ;)
Given I have two User classes, one located in /model/User.php and the other located in /assets/User.php
Now I want to have a class which extend the User class from /model/User.php, yet it seems that php is always looking for the one located in /assets/User.php. Any idea how does php determines which class to extend from?
Use namespaces
user.php:
class User {
function someMethod(){}
}
myUser.php:
namespace My;
class User {
function someMethod(){}
}
And you can use it like this:
include 'user.php';
include 'myuser.php';
$user1 = new User();
$user2 = new \My\User();
You can use namespaces: PHP Manual
You should (if you use) setup appropriately your spl_autoload_register function (http://php.net/manual/en/function.spl-autoload-register.php) to be able autoload the objects in files with same name, bud the names of object can not be the same.
You can distinguish the objects by name and path in which the files are stored.
For instance, you require core class in your bootstrap.php and define autoloading function:
<?php // bootstrap.php
require_once Core.php;
spl_autoload_register(array('Core', 'auto_load'));
$myUserClass = new MyClasses_User() // the required files for the class are loaded because of auto_load, PHP determines the right classes due to naming conventions: DirName_ClassName
?>
<?php // Core.php
define('DIR_SEPARATOR', '/');
class Core {
// Autoloading
public static function auto_load($class, $dir_classes = 'classes')
{
$file = str_replace('_', DIR_SEPARATOR, $class); // replace _ by / for path to file
if ($path = find_file_based_on_class_name($dir_classes, $file))
{
// Load the class file
require $path;
}
}
?>
<?php
// classes/Model/User.php
class Model_User {
// define the class
}
?>
// classes/Assets/User.php
<?php
class Assets_User {
// define the class
}
?>
// classes/MyClasses/User.php
<?php
class MyClasses_User exdends Model_User {
// define the class
}
?>