Using classes outside of the current directory in PHP - php

I have two folders in my directory:
Plugins
Classes
The Plugins folder contains two files: Sample.php and Plugins.php.
Sample.php is just a class with one function that extends the Plugins class. The Plugins class tries to create a new instance of the base class which is located in the Classes folder.
Plugins/Sample.php:
class Sample extends Plugins {
public $eggs;
public $pounds;
public function __construct() {
$this->eggs = "100";
$this->pounds = "10";
}
public function OnCall() {
echo "{$this->eggs} eggs cost {$this->pounds} pounds, {$this->name}!";
}
}
Plugins/Plugins.php:
class Plugins {
public $name;
public function __construct() {
include '../Classes/Base.php';
$base = new Base();
$this->name = $base->name;
}
}
Classes/Base.php:
class Base {
public $name = "Will";
public function Say() {
echo $this->name;
}
}
Index.php includes everything in the Plugins folder and is supposed to execute OnCall(). It is giving the following error messages:
Warning: include(../Classes/Base.php) [function.include]: failed to
open stream: No such file or directory in
/Applications/XAMPP/xamppfiles/htdocs/Plugins/Plugins/Plugins.php on
line 6
Warning: include() [function.include]: Failed opening
'../Classes/Base.php' for inclusion
(include_path='.:/Applications/XAMPP/xamppfiles/lib/php:/Applications/XAMPP/xamppfiles/lib/php/pear')
in /Applications/XAMPP/xamppfiles/htdocs/Plugins/Plugins/Plugins.php
on line 6
Fatal error: Class 'Base' not found in
/Applications/XAMPP/xamppfiles/htdocs/Plugins/Plugins/Plugins.php on
line 7
Index.php (if it helps):
foreach(glob('Plugins/*.php') as $file) {
require_once $file;
$class = basename($file, '.php');
if(class_exists($class)) {
$obj = new $class;
$obj->OnCall();
}
}
What I need to do is use the Base class in classes outside of the Classes folder. How can I do so?

You need to call the parent's constructor in your Sample class.
class Sample extends Plugins {
public $eggs;
public $pounds;
public function __construct() {
parent::__construct();
$this->eggs = "100";
$this->pounds = "10";
}
public function OnCall() {
echo "{$this->eggs} eggs cost {$this->pounds} pounds, {$this->name}!";
}
}

You probably want to take advantage of __autoload (http://ca1.php.net/manual/en/function.autoload.php)
Using this function will allow you to load up your classes easily no matter what directory they're in.
Simple Example:
function __autoload($classname) {
$path = "path/to/Classes/$classname.php";
if(file_exists($path)) {
require_once $path;
}
}
This means you would be able to remove your include statement from your Plugins class and simply keep the declaration $base = new Base(); and __autoload will be magically called and load up the correct file.

Related

PHP - Autoload only needed classes

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);
}
}
}
}

How can I make defined name globally available?

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

How to include a php and then remove it?

Well, I don't know if this post have the correct title. Feel free to change it.
Ok, this is my scenario:
pluginA.php
function info(){
return "Plugin A";
}
pluginB.php
function info(){
return "Plugin B";
}
Finally, I have a plugin manager that is in charge of import all plugins info to pool array:
Manager.php
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("pluginA.php", "pluginB.php");
foreach ($plugin_names as $name)
{
include_once $name;
$this->pool[] = info();
}
}
}
The problem here is that when I print pool array it only show me the info on the first plugin loaded. I supposed that the file inclusing override the info because it still calling the info() method from the first include.
Is there a way to include the info of both plugins having the info() function with the same name for all plugins files?
Thank you in advance
PS: a fatal cannot redeclare error is never hurled
you can use the dynamic way to create plugin classes
plugin class
class PluginA
{
public function info()
{
return 'info'; //the plugin info
}
}
manager class
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("pluginA", "pluginB"); //Plugin names
foreach ($plugin_names as $name)
{
$file = $name . '.php';
if(file_exists($file))
{
require_once($file); //please use require_once
$class = new $name(/* parameters ... */); //create new plugin object
//now you can call the info method like: $class->info();
}
}
}
}
Are you sure the interpreter isn't choking w/ a fatal error? It should be since you're trying to define the info function twice here.
There are many ways to achieve what you want, one way as in #David's comment above would be to use classes, eg.
class PluginA
{
function info() { return 'Plugin A'; }
}
class PluginB
{
function info() { return 'Plugin B'; }
}
then the Manager class would be something like this:
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("PluginA", "PluginB");
foreach ($plugin_names as $name)
{
include_once $name . '.php';
$this->pool[] = new $name();
}
}
}
Now you have an instance of each plugin class loaded, so to get the info for a plugin you would have $this->pool[0]->info(); for the first plugin. I would recommend going w/ an associative array though so you can easily reference a given plugin. To do this, the assignment to the pool would become:
$this->pool[$name] = new name();
And then you can say:
$this->pool['PluginA']->info();
for example.
There are many other ways to do it. Now that 5.3 is mainstream you could just as easily namespace your groups of functions, but I would still recommend the associative array for the pool as you can reference a plugin in constant time, rather than linear.

Create Simple Codeigniter library

For my current project i decided to create a library for some common functionalities.
Ex : Login_check,get_current_user etc.
With my little knowledge i created a simple one but unfortunately its not working.
Here my library :
FileName : Pro.php and located in application/libraries
class Pro{
public function __construct()
{
parent::_construct();
$CI =& get_instance();
$CI->load->helper('url');
$CI->load->library('session');
$CI->load->database();
}
function show_hello_world()
{
$text = "Hello World";
return $text;
}
}
?>
And i tried to load it on my controller :
<?php
class Admin extends CI_Controller
{
function __construct()
{
parent::__construct();
$this->load->database();
$this->load->library(array('session'));
$this->load->library("Pro");
}
function index()
{
echo($this->Pro->show_hello_world());
}
}
?>
I cant see any erros there...but i am getting a blank page.
Whats wrong with me ??
Thank you .
Edit : I got this error :
Call to a member function show_hello_world() on a non-object in C:\wamp\www\Project\application\controllers\admin.php on line 13
One thing I notice: remove the parent::__construct() from your library constructor, because it's not extending anything so has no parent to call.
Also, enable error reporting by setting the environment to "development" in index.php, and you might also want to raise the logging threshold to 4 in config/config.php so you log errors.
Try this simple test-case:
file Pro.php in application/libraries:
class Pro {
function show_hello_world()
{
return 'Hello World';
}
}
Controller admin.php in application/controllers
class Admin extends CI_Controller
{
function index()
{
$this->load->library('pro');
echo $this->pro->show_hello_world();
}
}
while your class name is capitalized, all your references to the library when loading it and using it should be lower case. you also do not need the constructor, as the other commenter mentioned.
so instead of:
echo($this->Pro->show_hello_world());
you should have:
echo($this->pro->show_hello_world());
I prefer the standard php autoloader approach, with this you dont need to change your classes at all, you can use your standard classes without modifications
say for instance you class is class 'Custom_Example_Example2' and is stored in libraries
in sub folders you can add this autoloader in the master index.php
make sure it is added below the defined APPPATH constant
//autoload custom classes
function __autoload($className) {
if (strlen(strstr($className, 'Custom_')) > 0 ||
strlen(strstr($className, 'Other1_')) > 0 ||
strlen(strstr($className, 'Other2_')) > 0) {
$exp = explode('_', $className);
$file = APPPATH.'libraries';
if(!empty($exp)) {
foreach($exp as $segment) {
$file .= '/'.strtolower($segment);
}
}
$file .= '.php';
require_once $file;
//debug
//echo $file.'<br />';
}
}
This will look for class calls matching the 'Custom_' prefix
and reroute them to the relative location in this case
you only need to define the base prefix not the sub folders / classes
these will be auto detected by this code
APPPATH.'libraries/custom/example/example2.php'
You can call it in the controller the standard php way
$class = new Custom_Example_Example2;
or
$class = new custom_example_example2();
You can modify the script to your liking currently it expects all folders and filenames in the library to be lowercase but you can remove the strtolower() function to allow multiple casing.
you can change the require once to echo to test the output by uncommenting this line and refresh the page, make sure you have a class init / test in the controller or model to run the test
echo $file.'<br />';
Thanks
Daniel
In Pro.php
class Pro{
protected $CI;
public function __construct() {
$this->CI = & get_instance();
}
public function showHelloWorld(){
return "Hello World";
}
}
In your controller
class Staff extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->database();
$this->load->helper(array('url_helper', 'url'));
$this->load->library("pro");
}
public function index() {
echo $this->pro->showHelloWorld();die;
}
}
Just do these things you can access your custom library in codeignitor.

php namespaces & autoload

I've read some posts about namespaces and autoload in php 5.3+, but still haven't succeeded in creating one working :x maybe some of you have an idea of what's going wrong about my code ?
Thank you previously.
Autoloader.php class
<?php
namespace my;
class AutoLoader {
private $aExt;
private $sPath;
protected static $instance;
public static function getInstance() {
if(!self::$instance instanceof self ) {
self::$instance = new self();
}
return self::$instance;
}
function __construct($sPath = __DIR__, $exts = 'php') {
// define path and extensions to include
$this->setPath($sPath);
$this->setExtensions($exts);
}
public function getPath() {
return $this->sPath;
}
public function setPath($path){
$this->sPath = $path;
}
public function removePath() {
unset ($this->sPath);
}
public function addExtension($ext) {
// prepends period to extension if none found
$this->aExt[$ext] = (substr($ext, 0, 1) !== '.') ? '.'.$ext : $ext;
}
public function removeExtension($ext) {
unset ($this->aExt[$ext]);
}
public function getExtensions() {
return $this->aExt;
}
public function setExtensions($extensions) {
// convert
if (is_string($extensions)) {
$extensions = array($extensions);
}
// add
foreach($extensions as $ext) {
$this->addExtension($ext);
}
}
public function register() {
set_include_path($this->sPath);
// comma-delimited list of valid source-file extensions
spl_autoload_extensions(implode(',',$this->aExt));
// default behavior without callback
spl_autoload_register(array($this, 'autoload'));
}
public function autoload($sClassName) {
include_once($sClassName.'.php');
return;
}
}
$autoloader = new AutoLoader();
$autoloader->register();
?>
MyClass.php the class i am trying to load dinamically
<?php
namespace my\tools;
class MyClass {
function __construct() {}
function __destruct() {}
function test() {
echo 'ok';
}
}
?>
index.php the caller
<?php
include_once('../Libraries/php/my/AutoLoader.php');
new my\tools\MyClass();
?>
and finally the class structures on my disk
Libraries
|_php
|_my
| |_Autoloader.php
|
|_MyClass.php
That's a tad bit over-engineered, my friend.
You might want to take a look at simply using PSR-0 (PRS-0 is now depreciated, PSR-4 is the new one), an autoloader specification from a large number of PHP projects, like phpBB, Joomla, CakePHP, Zend Framework and lots more. It's built with namespaces in mind, but works well with or without them.
The advantage of PSR-0 (or PSR-4) is that it leads to a clean, simple, obvious directory structure that an increasing number of projects are supporting. This means using one autoloader instead of a single autoloader for every single set of code.
spl_autoload_register() expects a valid callback. You give ... something ^^ But not a callback. A callback is
// a closure
$cb = function ($classname) { /* load class */ }
// object method
$cb = array($object, 'methodName');
// static class method
$cb = array('className', 'methodName');
// function
$cb = 'functionName';
See manual: spl_autoload_register() for further information and examples.
After searching a little on PHP.net website, the solution were really simple :/
In fact php autoload function MUST be on root namespace /, mine was on first level of my package (my/), when i moved the class to root namespace everything worked fine.

Categories