Dynamic function call from child namespace - php

I have several dictionaries in my application for internationalization. Depending on the version of the app, different languages should be included, so it is not necessary to deliver all available languages.
The setting which languages can be used, I want to set in constants for simplicity.
My dictionaries themselves are simple 'getDictionary()' functions that return an array of translations. To distinguish the different languages, the respective functions are in namespaces, e.g. Language\enGB and Language\esMX.
Dictionary for English (Great Britain).
namespace Language\enGB;
function getDictionary(): array
{
// ...
}
Dictionary for Spanish (Mexico).
namespace Language\enGB;
function getDictionary(): array
{
// ...
}
The parent class that uses these dictionaries is I18n. This class should dynamically include the dictionaries.
namespace Language;
// Include all available languages.
include_once __DIR__ . '/Dictionary/enGB.php';
include_once __DIR__ . '/Dictionary/enUS.php';
include_once __DIR__ . '/Dictionary/esMX.php';
// Supported languages that can be used by the client.
const SUPPORTED_LANGUAGES = ['en-GB', 'en-US'];
// Function definitions for all
const LANGUAGE_IMPORTS = [
'en-GB' => 'enGB\getDictionary',
'en-US' => 'enUS\getDictionary',
'es-MX' => 'esMX\getDictionary'
];
class I18n
{
private array $dictionary = [];
public function __construct(string $isoCode)
{
// Call all dictionary functions from all supported languages.
// Unsupported languages are not included.
foreach (SUPPORTED_LANGUAGES as $supportedLanguage) {
$functionName = LANGUAGE_IMPORTS[$supportedLanguage];
if (!function_exists($functionName)) continue;
$functionName();
}
}
}
Now I have the following problem: I cannot call the functions because they do not exist. The line if (!function_exists($functionName)) continue; always goes to the true case (continue).
I can't quite figure it out since 1) the functions have been included via include_once and 2) the namespace is specified with in the function name. Where is the problem here ?

Related

Cannot instantiate Object dynamically in PHP - Laravel [duplicate]

The autoloader works when I use it in index.php, but when I create an object within index.php and this object has to create other objects (which are all in the same namespace), it throws the error Uncaught Error: Class 'xxx' not found in (...).
My composer.json looks like this:
{
"autoload": {
"psr-4": {
"pizzaCase\\": "src",
"Connection\\": "src/Connection/",
"Elements\\": "src/Elements/"
}
},
"require": {
"cboden/ratchet": "^0.4"
}
}
My index.php looks like this:
<?php
require_once __DIR__. '/vendor/autoload.php';
require_once __DIR__."/src/config.php";
use Connection\Database;
use Elements\Form;
use Elements\FormElement;
use Elements\FormElementRadio;
// Database::init();
$form = new Form();
$data["options"] = "soemthing, something else";
$form->addElement("", "pizza", "", "Choose pizza", "radio", $data);
?>
In the addElement method I then create an object which is also within the src/Elements/ namespace, but it throws the error mentioned above.
The body of my addElement method looks like this:
<?php
namespace Elements;
class Form
{
public static $leftSize = 3;
protected $elements = [];
public function addElement($table, $name, $value, $label=false, $type = false, $data = false)
{
$type = ucfirst($type);
$class = "FormElement{$type}";
//FAILS HERE
if(class_exists($class))
{
//CLASS EXISTS, CREATE OBJECT FROM RESPECTIVE CLASS
$form = new $class($table, $name, $value, $label, $type, $data);
$this->elements[$name] = $form;
}
}
}
What am I doing wrong (or missing)? How come the autoloader can autoload it from index.php, but the object I create cannot create other objects without autoloader failing?
The difference is not to do with where the code is being run; the difference is that the failing code is trying to choose which class to load dynamically.
In PHP, namespaces are essentially a compile-time feature: before any of your code is run, the compiler looks at all references to class names which don't start with \, and prefixes them with the current namespace, or according to rules you've specified with use statements. When the code runs, the current namespace, and use statements, aren't visible at all.
When you specify a class name dynamically, the compiler just sees a string, not a class name, so leaves it alone. Then when the code runs, the class name looked up is assumed to be fully specified, not relative to the current namespace or use statements.
So the solution is simple - specify the full namespace when creating the dynamic class name:
$class = "Elements\FormElement{$type}";
You can also use the magic constant __NAMESPACE__ to have the compiler substitute the current namespace name for you (obviously, this still won't account for any use statements):
$class = __NAMESPACE__ . "\FormElement{$type}";
Alternatively, if you have a specific set of classes you are choosing between, you can use the ::class syntax to generate a string at compile time, based on the current namespace and any use statements in effect:
$mapTypeToClassName = [
'Radio' => FormElementRadio::class, // expands to 'Elements\FormElementRadio'
'Select' => FormElementSelect::class,
// etc
];
$class = $mapTypeToClassName[$type];
It could be because you’re having multiple namespaces for the src directory.
Usually you would just create a namespace for src like this
“psr-4": {
"PizzaCase\\": "src"
}
And then just use PizzaCase\Elements and PizzaCase\Connections as namespaces

list folders where Codeigniter searches for classes

Codeigniter does not find trivial classes:
Unable to load the requested class: Bcrypt
But the same goes for custom made classes defined in files in application/libraries/. I am used that django lists the folders where it searched for a file, but did not find one. Obviously CI must also iterate over some list of folders or files, but is not as polite to display them along with the error.
It seems as if CI has a naming convention to deduce the (set of) filename(s) where it would expect a class to be. How can I programmatically error_log the list of folders or filenames that Codeigniter or PHP tried to track down this class?
EDIT: The lines of code that produce such a loading-error are:
$autoload['libraries'] = array('database','session','mi_file_fetcher');
in application/config/autoload.php and
$this->load->library("bcrypt");
in application/models/User.php
As stated in the comments, I was not asking for a fix, I was asking for a list.
I managed to do so by updating system/core/Loader.php
protected function _ci_load_library_files_tried($class, $subdir, $params, $object_name)
{
$files_tried = array(BASEPATH . 'libraries/' . $subdir . $class . '.php');
foreach ($this->_ci_library_paths as $path) {
if ($path === BASEPATH) {
continue;
}
array_push($files_tried, $path . 'libraries/' . $subdir . $class . '.php');
}
return $files_tried;
}
protected function _ci_load_library($class, $params = NULL, $object_name = NULL)
{
// ...
// If we got this far we were unable to find the requested class.
$files_tried = $this->_ci_load_library_files_tried($class, $subdir, $params, $object_name);
log_message('error', 'Unable to load the requested class: '.$class .
", tried these files:\n" . join("\n", $files_tried));
show_error('Unable to load the requested class: '.$class .
', tried these files:<ul><li>' . join('</li><li>', $files_tried) . '</li></ul>');
}
Would be great if CI actually provided decent debugging information.
The CodeIgniter (CI) documentation does tell you the default locations of libraries, models, helpers, views and many other framework objects. There isn't a section that explicitly lists the folders though. The Loader Class documentation does tell you, but you have to dig for it a bit.
The subtopics on the General Topics section of the docs clearly state the default locations for the various classes the frameworks uses and where to put custom classes.
In most cases following the prescribed file structure and using the loader class, e.g. (Bcrypt.php is in /application/libraries/)
$this->load->library('bcrypt');
works perfectly.
There are cases (none of which seem to be involved in your problem) where CI needs help. Rather than hack or extend CI_Loader an autoloader is useful in these cases.
There are lots of ways to add an autoloader but my preference is to use CI's Hooks feature. Here's how.
In config.php set 'enable_hooks' to TRUE
$config['enable_hooks'] = TRUE;
These lines go in /application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
The following is the contents of /application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
/**
* Allows classes that do not start with CI_ and that are
* stored in these subdirectories of `APPPATH`
* (default APPPATH = "application/" and is defined in "index.php")
* libraries,
* models,
* core
* controllers
* to be instantiated when needed.
* #param string $class Class name to check for
* #return void
*/
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'controllers/'.$class.'.php'))
{
require_once $file;
}
}
}
The function log_message($level, $message) could be used in the above if you wanted.
If you are using some other creative folder structure you will have to modify the above to accommodate that layout.

Multiple functions of the same name

I am looking to create an extension api for my web application.
Example extension file:
function echoCustomHeaders(){
echo '<li>Header Link</li>';
}
There would be several files similar to the example extension file (with the same function name, for user friendlyness when programming addons).
for($x=0;$x<count($extension_files);$x++){
//This would obviosely break when it gets to the second file, as functions cannot be declared twice in php
require_once($extension_files[$x]);
}
//some code later...
//this should call echoCustomHeaders() in ALL of the extension files, what code should I put here to make this happen?
echoCustomHeaders();
In case you are wondering about what the question is, read the comments in the code above and it should be fairly easy to see.
Return closures (lambda expressions) in your extension files as follows:
return function(){
echo '<li>Header Link</li>';
}
In PHP the include/require statement is really a function and therefore has a return value, hence you can collect those closures into an array:
$closures = array();
for($x=0;$x<count($extension_files);$x++){
$closures[$i]=include($extension_files[$x]);
}
// Do whatever you want with your closures, e.g. execute them:
foreach($closures as $closure) {
$closure();
}
ADDED CONTENT:
In the case if you would like to return multiple closures with each include, you may return an array of closures, indexed by the name of them:
return array(
'echoCustomHeaders' => function() {
echo '<li>Header Link</li>';
},
// ...
);
Then you can still execute some of them by their name:
$closureArray = array();
foreach($extension_files as $file) {
$closureArray[] = include($file);
}
foreach($closureArray as $closure) {
if(isset($closure['echoCustomHeaders'])) // Maybe there wasn't an echoCustomHeaders in each extension file ...
$closure['echoCustomHeaders']();
}
Maybe it would be a better idea to even separate the different kind of extension functions into distinct arrays:
$closureArray = array();
foreach($extension_files as $file) {
$functions = include($file);
foreach($functions as $name => $function) {
if(!isset($closureArray[$name]))
$closureArray[$name] = array();
$closureArray[$name][] = $function;
}
}
foreach($closureArray['echoCustomHeaders'] as $closure) {
$closure();
}
Another solution is to use a more object oriented way, and declare a new extension class in each extension file. However, if there would be no data sharing required between the extension methods in an extension file, then simply returning the functions as an array of closures is a more lightweight and cleaner solution in my opinion.
1.maybe you can use the new feature after php5.3:namespace http://www.php.net/manual/en/language.namespaces.php, then you can use the same name functions.
2.however you could think about the object oriented solution,for example,defined a base class who has a method echoCustomHeaders.

Autoload classes from different folders

This is how I autoload all the classes in my controllers folder,
# auto load controller classes
function __autoload($class_name)
{
$filename = 'class_'.strtolower($class_name).'.php';
$file = AP_SITE.'controllers/'.$filename;
if (file_exists($file) == false)
{
return false;
}
include ($file);
}
But I have classes in models folder as well and I want to autoload them too - what should I do? Should I duplicate the autoload above and just change the path to models/ (but isn't this repetitive??)?
Thanks.
EDIT:
these are my classes file names in the controller folder:
class_controller_base.php
class_factory.php
etc
these are my classes file names in the model folder:
class_model_page.php
class_model_parent.php
etc
this is how I name my controller classes class usually (I use underscores and lowcaps),
class controller_base
{
...
}
class controller_factory
{
...
}
this is how I name my model classes class usually (I use underscores and lowcaps),
class model_page
{
...
}
class model_parent
{
...
}
I see you are using controller_***** and model_***** as a class naming convention.
I read a fantastic article, which suggests an alternative naming convention using php's namespace.
I love this solution because it doesn't matter where I put my classes. The __autoload will find it no matter where it is in my file structure. It also allows me to call my classes whatever I want. I don't need a class naming convention for my code to work.
You can, for example, set up your folder structure like:
application/
controllers/
Base.php
Factory.php
models/
Page.php
Parent.php
Your classes can be set up like this:
<?php
namespace application\controllers;
class Base {...}
and:
<?php
namespace application\models;
class Page {...}
The autoloader could look like this (or see 'a note on autoloading' at the end):
function __autoload($className) {
$file = $className . '.php';
if(file_exists($file)) {
require_once $file;
}
}
Then... you can call classes in three ways:
$controller = new application\controllers\Base();
$model = new application\models\Page();
or,
<?php
use application\controllers as Controller;
use application\models as Model;
...
$controller = new Controller\Base();
$model = new Model\Page();
or,
<?php
use application\controllers\Base;
use application\models\Page;
...
$controller = new Base();
$model = new Page();
EDIT - a note on autoloading:
My main auto loader looks like this:
// autoload classes based on a 1:1 mapping from namespace to directory structure.
spl_autoload_register(function ($className) {
# Usually I would just concatenate directly to $file variable below
# this is just for easy viewing on Stack Overflow)
$ds = DIRECTORY_SEPARATOR;
$dir = __DIR__;
// replace namespace separator with directory separator (prolly not required)
$className = str_replace('\\', $ds, $className);
// get full name of file containing the required class
$file = "{$dir}{$ds}{$className}.php";
// get file if it is readable
if (is_readable($file)) require_once $file;
});
This autoloader is a direct 1:1 mapping of class name to directory structure; the namespace is the directory path and the class name is the file name. So the class application\controllers\Base() defined above would load the file www/application/controllers/Base.php.
I put the autoloader into a file, bootstrap.php, which is in my root directory. This can either be included directly, or php.ini can be modified to auto_prepend_file so that it is included automatically on every request.
By using spl_autoload_register you can register multiple autoload functions to load the class files any which way you want. Ie, you could put some or all of your classes in one directory, or you could put some or all of your namespaced classes in the one file. Very flexible :)
You should name your classes so the underscore (_) translates to the directory separator (/). A few PHP frameworks do this, such as Zend and Kohana.
So, you name your class Model_Article and place the file in classes/model/article.php and then your autoload does...
function __autoload($class_name)
{
$filename = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class_name)).'.php';
$file = AP_SITE.$filename;
if ( ! file_exists($file))
{
return FALSE;
}
include $file;
}
Also note you can use spl_autoload_register() to make any function an autoloading function. It is also more flexible, allowing you to define multiple autoload type functions.
If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once.
Edit
Note : __autoload has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged. Please refer to PHP documentation for more details. http://php.net/manual/en/function.autoload.php
I have to mention something about "good" autoload scripts and code structure, so read the following CAREFULLY
Keep in Mind:
Classname === Filename
Only ONE class per file
e.g: Example.php contains
class Example {}
Namespace === Directory structure
e.g: /Path1/Path2/Example.php matches
namespace Path1\Path2;
class Example {}
There SHOULD be a Root-Namespace to avoid collisions
e.g: /Path1/Path2/Example.php with root:
namespace APP\Path1\Path2;
class Example {}
NEVER use manually defined path or directory lists, just point the loader to the top most directory
Keep the loader AS FAST AS POSSIBLE (because including a file is expensive enough)
With this in mind, i produced the following script:
function Loader( $Class ) {
// Cut Root-Namespace
$Class = str_replace( __NAMESPACE__.'\\', '', $Class );
// Correct DIRECTORY_SEPARATOR
$Class = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, __DIR__.DIRECTORY_SEPARATOR.$Class.'.php' );
// Get file real path
if( false === ( $Class = realpath( $Class ) ) ) {
// File not found
return false;
} else {
require_once( $Class );
return true;
}
}
Where to place it..
/Loader.php <-- there goes the loader
/Controller/... <-- put ur stuff here
/Model/... <-- or here, etc
/...
Remeber:
if you use a root namespace, the loader has to be in this namespace too
you may prefix $Class to match your needs (controller_base {} -> class_controller_base.php)
you may change __DIR__ to an absolute path containing your class files (e.g. "/var/www/classes")
if you don't use namespaces, all files has to be in the same directory together with the loader (bad!)
Happy coding ;-)
A little review at other answers:
THIS IS JUST MY PERSONAL OPINION - NO OFFENSE INTENDED!
https://stackoverflow.com/a/5280353/626731
#alex good solution, but don't make you class names pay for bad file structures ;-)
this is job for namespaces
https://stackoverflow.com/a/5280510/626731 #Mark-Eirich it works, but its pretty nasty/ugly/slow/stiff[..] style to do it this way..
https://stackoverflow.com/a/5284095/626731 #tealou for his problem to be solved this is the most clear approach so far :-) ..
https://stackoverflow.com/a/9628060/626731 #br3nt this reflects my point of view, but please(!) .. dont use strtr!! .. which brings me to:
https://stackoverflow.com/a/11866307/626731 #Iscariot .. to you, a little "you-know-bullshit-benchmark:
Time sprintf preg_replace strtr str_replace v1 str_replace v2
08:00:00 AM 1.1334 2.0955 48.1423 1.2109 1.4819
08:40:00 AM 1.0436 2.0326 64.3492 1.7948 2.2337
11:30:00 AM 1.1841 2.5524 62.0114 1.5931 1.9200
02:00:00 PM 0.9783 2.4832 52.6339 1.3966 1.4845
03:00:00 PM 1.0463 2.6164 52.7829 1.1828 1.4981
Average 1.0771 2.3560 55.9839 1.4357 1.7237
Method Times Slower (than sprintf)
preg_replace 2.19
strtr 51.97
str_replace v1 1.33
str_replace v2 1.6
Source: http://www.simplemachines.org/community/index.php?topic=175031.0
Questions?.. (But he is in fact right about full path including)
https://stackoverflow.com/a/12548558/626731 #Sunil-Kartikey
https://stackoverflow.com/a/17286804/626731 #jurrien
NEVER loop in time critical environment! Don't search for files on os! - SLOW
https://stackoverflow.com/a/21221590/626731 #sagits .. much better than Marks ;-)
function autoload($className)
{
//list comma separated directory name
$directory = array('', 'classes/', 'model/', 'controller/');
//list of comma separated file format
$fileFormat = array('%s.php', '%s.class.php');
foreach ($directory as $current_dir)
{
foreach ($fileFormat as $current_format)
{
$path = $current_dir.sprintf($current_format, $className);
if (file_exists($path))
{
include $path;
return ;
}
}
}
}
spl_autoload_register('autoload');
Here is my solution,
/**
* autoload classes
*
*#var $directory_name
*
*#param string $directory_name
*
*#func __construct
*#func autoload
*
*#return string
*/
class autoloader
{
private $directory_name;
public function __construct($directory_name)
{
$this->directory_name = $directory_name;
}
public function autoload($class_name)
{
$file_name = 'class_'.strtolower($class_name).'.php';
$file = AP_SITE.$this->directory_name.'/'.$file_name;
if (file_exists($file) == false)
{
return false;
}
include ($file);
}
}
# nullify any existing autoloads
spl_autoload_register(null, false);
# instantiate the autoloader object
$classes_1 = new autoloader('controllers');
$classes_2 = new autoloader('models');
# register the loader functions
spl_autoload_register(array($classes_1, 'autoload'));
spl_autoload_register(array($classes_2, 'autoload'));
I'm not sure whether it is the best solution or not but it seems to work perfectly...
What do you think??
My version of #Mark Eirich answer:
function myload($class) {
$controllerDir = '/controller/';
$modelDir = '/model/';
if (strpos($class, 'controller') !== false) {
$myclass = $controllerDir . $class . '.php';
} else {
$myclass = $modelDir . $class . '.inc.php';
}
if (!is_file($myclass)) return false;
require_once ($myclass);
}
spl_autoload_register("myload");
In my case only controller class have the keyword in their name, adapt it for your needs.
Simpliest answer I can give you without writing down those complex codes and even without using the namespace (if this confuses you)
Sample Code. Works 100%.
function __autoload($class_name){
$file = ABSPATH . 'app/models/' . $class_name . '.php';
if(file_exists($file)){
include $file;
}else{
$file = ABSPATH . 'app/views/' . $class_name . '.php';
if(file_exists($file)){
include $file;
}else{
$file = ABSPATH . 'app/controllers/' . $class_name . '.php';
include $file;
}
}
I guess the logic is explainable itself. Cheers mate! Hope this helps :)
Here's what I'd do:
function __autoload($class_name) {
$class_name = strtolower($class_name);
$filename = 'class_'.$class_name.'.php';
if (substr($class_name, 0, 5) === 'model') {
$file = AP_SITE.'models/'.$filename;
} else $file = AP_SITE.'controllers/'.$filename;
if (!is_file($file)) return false;
include $file;
}
As long you name your files consistently, like class_controller_*.php and class_model_*.php, this should work fine.
Everyone is is coping and pasting things from code they got off the internet (With the exception of the selected answer). They all use String Replace.
String Replace is 4 times slower than strtr. You should use it instead.
You should also use full paths when including classes with autoloading as it takes less time for the OS to resolve the path.
__autoload() function should not be use because it is not encourged. Use spl_autoload(), spl_autoload_register() instead. __autoload() just can load one class but spl_autoload() can get more than 1 classes. And one thing more, in future __autoload() may deprecated. More stuff can be find on http://www.php.net/manual/en/function.spl-autoload.php
Altough this script doesn't have the name convention and this thread is already a bit old, in case someone is looking of a possible answer, this is what I did:
function __autoload($name) {
$dirs = array_filter(glob("*"), 'is_dir');
foreach($dirs as $cur_dir) {
dir_searcher($cur_dir, $name);
}
}
function dir_searcher($cur_dir, $name) {
if(is_file("$cur_dir/$name.php")) {
require_once "$cur_dir/$name.php";
}
$dirs = array_filter(glob($cur_dir."/*"), 'is_dir');
foreach($dirs as $cdir) {
dir_searcher("$cdir", $name);
}
}
not sure if it is really optimal, but it searches through the folders by reading dir recursively. With a creative str_replace function you can get your name cenvention.
I use this. Basically define your folder structure (MVC etc) as a constant in a serialised array. Then call the array in your autoload class. Works efficiently for me.
You could obviously create the folder array using another function but for MVC you may as well type it in manually.
For this to work you need to call your classes ...... class.classname.php
//in your config file
//define class path and class child folders
define("classPath","classes");
define("class_folder_array", serialize (array ("controller", "model", "view")));
//wherever you have your autoload class
//autoload classes
function __autoload($class_name) {
$class_folder_array = unserialize (class_folder_array);
foreach ($class_folder_array AS $folder){
if(file_exists(classPath."/".$folder.'/class.'.$class_name.'.php')){require_once classPath."/".$folder.'/class.'.$class_name.'.php';break;}
}
}

File private variables in PHP

Is it possible to define private variables in a PHP script so these variables are only visible in this single PHP script and nowhere else? I want to have an include file which does something without polluting the global namespace. It must work with PHP 5.2 so PHP namespaces are not an option. And no OOP is used here so I'm not searching for private class members. I'm searching for "somewhat-global" variables which are global in the current script but nowhere else.
In C I could do it with the static keyword but is there something similar in PHP?
Here is a short example of a "common.php" script:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
When I include this file in some script then the $dir variable is visible in all other scripts as well and I don't want that. So how can I prevent this?
There are a few things you could do to keep $dir out of subsequent files
Example 1
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
This is the most obvious.
Example 2
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// work with $dir
unset($dir);
Just unset the variable after defining it and using it. Note this will unset any variable named $dir used prior to including this script.
Example 3
define('DIR_THIS', dirname(__FILE__));
set_include_path(DIR_THIS . PATH_SEPARATOR . get_include_path());
It is less likely I suppose to redefine a global constant like this.
Example 4
function my_set_include_path {
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
$my_other_var = 'is trapped within this function';
}
my_set_include_path();
You can define as many variables within that function and not affect the global namespace.
Conclusion
The first method is the easiest way to solve this problem, however because you want to use $dir again, it may not be ideal. The last example will at least keep that $dir (and any others defined in that function) out of the global namespace.
The only way you're going to accomplish anything close to what you want is to wrap everything in that included file in a function, and call it. If the file needs to execute itself you could still do
<?php
run_myfile()
function run_myfile() {
...
}
?>
There is no generic way to make a variable scoped to only a file outside of namespaces, classes, or functions.
Well, I'm probably getting flailed for this, but you if you are totally desperate you could use a Registry for that. I've whipped up a small one that does without classes (since I assume from And no OOP is used here so I'm not searching for private class members. means you don't want to do it with OOP at all)
function &registry_get_instance()
{
static $data = array();
return $data;
}
The static $data variable inside is persisted inside the function scope, so you can call the function wherever you like and always get the same contents. The crucial point is returning by reference, e.g.
$registry = &registry_get_instance(); // get $data array by reference
$registry['foo'] = 'bar'; // set something to $data
unset($registry); // delete global reference to $data
print_r(&registry_get_instance()); // show $data
Obviously you'd still have $registry as a variable in the global scope when calling this method from the global scope. So, you could add some more functions to make the Registry more convenient to use, e.g. for setting data to the Registry:
function registry_set($key, $value)
{
$registry = &registry_get_instance();
$registry[$key] = $value;
}
and for getting it out again:
function registry_get($key)
{
$registry = &registry_get_instance();
if(array_key_exists($key, $registry)) {
return $registry[$key];
} else {
trigger_error(sprintf(
'Undefined Index: %s', htmlentities($key)
), E_USER_NOTICE);
}
}
and for checking if a key exists:
function registry_isset($key)
{
$registry = &registry_get_instance();
return array_key_exists($key, $registry);
}
which you could then use like:
registry_set('foo', 'bar'); // setting something to the registry
var_dump( registry_isset('foo') ); // check foo is in the registry now
echo registry_get('foo'); // prints 'bar'
echo registry_get('punt'); // raises Notice
You could populate the Registry from an include file with an additional method like this:
function registry_load_file($file)
{
if(!is_readable(realpath($file))) {
return trigger_error(sprintf(
'File is not readable: %s', htmlentities($file)
), E_USER_WARNING);
}
$config = include $file;
if(!is_array($config)) {
return trigger_error(sprintf(
'Expected file %s to return an array', htmlentities($file))
, E_USER_WARNING);
}
$registry = &registry_get_instance();
$registry += $config;
}
with the include file having to return an array:
// config.php
return array(
'setting1' => 'something'
);
and then you can do
registry_load_from_file('config.php'); // add the contents of config to registry
print_r(registry_get_instance()); // show content of registry
Of course, this is now six functions in the global scope just for not having a global variable. Don't know if it's worth it, especially since I consider static in functions and all that reference stuff doubtful practice.
Take it as a proof of concept :)
Why not just put everything in a static class? Then you only have a single "variable" that could possibly conflict with the global namespace.
class MyClass {
public static $myvar = 1;
public static $myvar2 = "xyz";
public static function myfunction() {
self::$myvar++;
self::$myvar2 = "abc";
}
}
// References to class items, if needed
MyClass::myfunction();
MyClass::$myvar += 3;
If the problem you are trying to is just:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
Then the solution would be to change the include path relative to '.' in your ini settings. E.g. change:
include_path=includes:/usr/local/php
to
include_path=./includes:/usr/local/php
Note that a script does not come into scope except where you explicitly include/require it (both the _once check applies globally) however I would recommend strongly against calling include/require from within a function - its much more transparent having the includes/requires at the top of the script.
I think that the problem you are trying to solve is based on a false premise and you should look for another way of fixing it. If you want the code in an include file to behave differently depending on what includes it, then really you should seperate it out into 2 seperate files - or maybe even 3 - 2 for the different behaviours and 1 for the common.
C.

Categories