Related
i have an application done in php and all configuration variables are loaded in a big $conf variable at the beginning of the script.
What is the better way to communicate this configuration variable to all other functions ?
make it a parameter of every function ? or use it with "global $conf;" statement in every function ?
is there a better way to do ?
Thanks
Use PHP constants.
For ponies sake, avoid using global variables at all costs :)
EDIT
Some explanations about "avoiding global variables at all costs" and possible alternatives:
https://stackoverflow.com/questions/357187/when-are-global-variables-acceptable/357361#357361
http://my.opera.com/zomg/blog/2007/08/30/globals-are-evil
https://stackoverflow.com/questions/1285700/what-are-some-good-tips-for-a-new-php-developer (especially section Scope of the accepted answer)
Make a configuration class that stores the options. Make it a singleton PHP Manual describes that here. This is just an alternative to global variables. It would allow you to define a method to load options from a file or a php array and store them in the class. Other classes can use the configuration object by getting the single instance and accessing the data.
I think this is better than a global variable as the other answer also says. But it still lets you define options as arrays, or even nested arrays if you want (and set up your class accordingly)
Your use of a single global-scoped variable $conf is perfectly fine. Many PHP applications do that. But there are drawbacks to combat.
In particular it's often more effort to write global $conf in each function where you want to access them. In that case I would recommend a simple global wrapper function instead:
function conf($key, $sub="") {
global $conf;
if (defined($key))
{ return constant($key); }
elseif ($sub)
{ return $conf[$key][$sub]; }
else
{ return $conf[$key]; }
}
This allows you to write conf("setting1") or conf("main", "opt3") whereever you need it. Still you can access the global $conf where that is more suitable. As extra bonus you can make this wrapper function more intelligent, by allowing it to query alternative settings etc. Also see how easy it is to also sneak in conf("CONSTANT") support.
Keeping this adds some flexibility in defining your configuration settings. Personally I use a similar approach, albeit with defining the array step-wise rather than at once:
$app_config["title"] = ...;
$app_config["editor.btns"] = ...;
define("RESTRICTED_MODE", true);
I'm preferring the array() approach, but transitioning to an ini-file for storage at a later point is not a problem. Also you can still make your config array read-only if the need arises. For that just define an:
class Read_Only_Array extends ArrayObject { function offsetSet() {} }
$conf = new ReadOnlyArray($conf);
So it's still accessible as array, but you easily established what others use cumbersome registries or syntactic workarounds for.
The "globals are evil" meme is completely baloney. It's parrotted on SO by cargo cult programmers with a desire for oversimplification and newcomers who glance over bold headlines without understanding the language semantics.
In your case, you just use a single $conf variable, and do not pollute the shared scope. When it is coherently accessed from the whole application, then it's not an issue. You should however strictly avoid to modify contents at runtime (use Read_Only_Array if need be). Create a secondary $app_var[] aray for that, and keep your config settings static.
Because I noticed it was a pattern I was constantly re-implementing, I made a DataContainer class that overrides __set,__get,__isset,__unset, and implements ArrayAccess, Countable, IteratorAggregate, and Serializable
For example, my View class, which renders PHP templates in an MVC fashion, inherits from DataContainer so that it has overloaded access to the data that gets supplied to the template.
Now, I am finding myself wanting to create a Session class to abstract away from PHP's low-level session handling. It occured to me that this Session class would do just about everything DataContainer does, and in fact, is-a DataContainer - it holds data.
However, if I inherit from DataContainer, then all the overloaded calls go to its private $_data array. Of course, I can override DataContainer's public get($key), public set($key,$val), etc methods, but the only thing I would be doing is renaming $this->_data to $_SESSION.
Is it possible to set a variable as a reference to a special global like $_SESSION?
class Session extends DataContainer {
//singleton stuff
private function __construct() {
$this->_data =& $_SESSION;
}
}
Is it even a good idea to do this? If not, what do you suggest?
Is it possible to set a variable as a
reference to a special global like
$_SESSION?
Yes, $this->_data =& $_SESSION;
Is it even a good idea to do this?
I don't see why not, one could argue that it may be better to pass the data in to the constructor by reference so that it can be used for any array not just session.
Thus, Yes.
edit: as a side point, remember you don't always have a session, sometimes your running on cli etc, personally I have my own session object (just a DataContainer like yours) which I then persist to $_SESSION where needed, or file or.. - ie I save (stateful) session objects in the $_SESSION rather than use the $_SESSION as the session data, if that makes sense..
I sure hope it's good idea, as I use it constantly. Kind of a Decorator, only for a variable-container. Yes, it has worked for for about 3 years now, and I very much like the validating & tracing capabilities it yields on more complex projects. Keep in mind you cannot force any other code to use the container instead of $_SESSION, but a project wide search for that particular string yields fast results when the majority of the code uses other means.
I also make it a Singleton for those moments a projects is not suited for a proper dependancy-injection path, either for size, time or historical reasons. Referencing a Session::instance() is about as easy as the $_SESSION superglobal.
I have a program that I use on several sites. It uses require('config.php'); to set any site dependant variables like mysql connect info, paths, etc.
Let's say that I use one of these site-dependant variables in a function, like $backup_path.
This variable was initially declared in config.php, and does not appear in the main program file.
I need to access this variable in function makebackup($table_name); (also in a separate functions.php file).
Is it better to say
makebackup('my_table');
and then use "global $backup_path" inside the function, or is it better to call the function using
makebackup('my_table',$backup_path);
The argument for the first is that it keeps the main program flow simple and easy to understand, without clutter.
The argument for the second is that it might not be obvious that the variable $backup_path exists after some time has passed, and debugging or reworking could be difficult.
Is one or the other of these techniques "standard" among professional programmers? Or should I be using $_SESSION to declare these global variables?
The second alternative,
makebackup('my_table', $backup_path);
is a reusable function and therefore generally preferable. The extra argument is not a big price for reusability.
If you are entirely sure that you'll ever use that function in that particular application only, and for $backup_path only, then maybe consider the global alternative. Even then it's good to check that the global variable actually exists. And be aware that it's extremely difficult to get rid of globals once you start using them.
Remember that you can set a default value for your function:
function makebackup($table, $dir = CONFIG_BACKUP_PATH)
That way you won't have to supply the variable in the default case, you can simply assume that the configured backup path is the default.
This assumes that you are using constants, not global variables.
I'm think you must use Singleton of Factory class config for this purposes.
function makebackup($table)
{
$backup_path = ConfigFactory().getConfig($some_site_specific_data).getBackupPath()
mysqldump($table, $backup_path)
}
Passing references around is far easier to test (you can give mock configuration objects). Globals less so. You can assert that the reference is not null on the method. I would call testability best practice.
Label that global variable
Personally, I have also taken to marking global variables very clearly. If I must use them, I want to be clear about them.
So here, I'd rename $backup_path to $GLOBAL_backup_path. Every time I saw it, I'd know to be careful with it.
An alternative option is using php constants with define().
Your config.php will set constants for every parameter (mysql connection, css style, wathever). Then you will not need to pass variables to functions nor using global.
One downside is that you can define only booleans, floats, strings or integers, no complex data structures.
Not sure there's really a 'right' way to do it, but another option would be something like this:
function makebackup($table, $backup_path = '') {
if ( $backup_path == '' ) {
if ( isset($GLOBALS['backup_path']) ) {
$backup_path = $GLOBALS['backup_path'];
}
else {
die('No backup path provided');
}
}
}
That way you can either pass in the value (for testing and future use) or if you don't pass it in, then the function will look for a possible global variable.
I've got a site setup that, on page load, turns all user submitted strings into SafeString objects. For those unfamiliar with SafeString, it basically forces the user to echo out sanitized data preventing XSS and whatnot..
Anyways, there's a problem. My $_SESSION array is being filled with __PHP_Incomplete_Class Object. From what I've read, this is due to not initializing the class before the session and then storing class objects in the session.
Here's my code:
require_once __WEBROOT__ . '/includes/safestring.class.php';
$temp = array
(
&$_SERVER, &$_GET, &$_POST, &$_COOKIE,
&$_SESSION, &$_ENV, &$_REQUEST, &$_FILES,
&$HTTP_SERVER_VARS, &$HTTP_GET_VARS,
&$HTTP_POST_VARS, &$HTTP_COOKIE_VARS,
&$HTTP_POST_FILES, &$HTTP_ENV_VARS
);
function StringsToSafeString(&$array)
{
foreach ($array as $key => $value)
{
if (is_string($array[$key]))
{
$array[$key] = new SafeString($value);
}
if (is_array($array[$key]))
{
StringsToSafeString($array[$key]);
}
}
}
StringsToSafeString($temp);
unset($temp);
I can't think of a way to rewrite this which would solve the problem :/
Any ideas?
When you're accessing $_SESSION, you're not just changing the current script's copy of the data read from the session, you're writing SafeString objects back into the active session.
But putting custom objects in the session is dodgy and something I would generally try to avoid. To be able to do it you have to have defined the class in question before calling session_start; if you don't, PHP's session handler won't know how to deserialise the instances of that class, and you'll end up with the __PHP_Incomplete_Class Object.
So avoid frobbing the session. If you must take this approach, make a copy of the data from $_SESSION into a local $mysession array. However, I have to say I think the whole idea of a SafeString is dangerous and unworkable; I don't think this approach is ever going to be watertight. Whether a string of raw text is ‘safe’ is nothing to do with where it came from, it is a property of how you encode it for the target context.
If you get another text string from a different source such as the database, or a file, or calculated within the script itself, it needs exactly the same handling as a string that came from the user: it needs to be htmlspecialcharsed. You're going to have to write that escape anyway; the safestring gains you nothing. If you need to send the string to a different destination format, you would need a different escape.
You cannot encapsulate all string processing problems into one handy box and never think about them again; that's just not how strings work.
I know it's been years since this was asked, but I'm posting my answer because none of the answers above actually explain to the OP what is actually wrong.
PHP serializes its sessions using the built-in serialize and unserialize methods. serialize of PHP has the ability to serialize PHP objects (aka class instances) and convert them to string. When you unserialize those strings, It converts them back those same classes with those values. Classes who have some private properties and want to encode/decode that or do something complex in their serialization/deserialization implement the Serializable class and add serialize and unserialize methods to the class.
When PHP's unserialize tries to unserialize a class object, but the class name isn't declared/required, instead of giving a warning or throwing an Exception, it converts it to an object of __PHP_Incomplete_Class.
If you don't want your session objects to convert to __PHP_Incomplete_Class, You can do it by either requiring the class files before you invoke session_start, or by registering an autoload function.
You just have to include the safestring.class.php before you call session_start() when you want to read the SafeString objects from $_SESSION variable:
<?php
require_once __WEBROOT__ . '/includes/safestring.class.php';
session_start();
print_r($_SESSION);
and yeah, if you are using PHP framework that (most probably) calls session_start() internally, make sure you require_once the class file beforehand (use hooks or whatever mechanisms that the framework provides).
I solved the problem using json_encode and json_decode function.
This is where I wanted to assign the value to session.
$user_json = json_encode($user);
$_SESSION['user'] = $user_json;
This is where I show the user after decoding the json
session_start();
$user_json= $_SESSION['user'];
$user = json_decode($user_json);
This solves my problem but I am not sure about performance or security. I haven't checked them.
Lukman's answer is correct. But you already mention that in your question, so apparently you can't instantiate the class before the session starts, for some reason.
You may want to check if sessions start automatically in the php config:
http://www.php.net/manual/en/session.configuration.php#ini.session.auto-start
If they are and yu cant help that, you may want to check if you can have your classes autoloaded prior to that:
http://php.net/manual/en/language.oop5.autoload.php
If all else fails, you can still serialize the objects before you store them in a session, and unserialize them each them you retrieve them:
http://php.net/manual/en/function.serialize.php
I dont see in your code where you store your variables, but it would be something like
$mystuff = unserialize($_SESSION["mystuff"]);
$mystuff->dostuff();
$_SESSION["mystuff"] = serialize($mystuff);
Be sure to load the class definition before you unserialize your variables
$2c,
*-pike
I just dealt with something like this. Took me hours to finally find how my order was screwed.
I had a file being called asynchronously.
myFile.php
that file contained the following..
$result = include ("myOtherFile.php");
return $result;
Myotherfile.php has something like this
require_once "lib/myClassLibs.php";
require_once "webconfig.php";
the webconfig.php had the session_start() call in it.
The lib/myClassLibs has all the class info init. If you check before the webconfig call, you can see that the class is available.
If you check before the webconfig call, you will also see that the session has started already. If you check before the lib/myClassLibs.php, you will see the session is already started.
Checking in myFile.php before you include MyOtherFile.php, you find the session has not started.
This represented legacy code that has worked for the last 8 years without me fiddling with it. I pulled the includes out of the "MyOtherFile.php". Now my sessions are synching properly.
I solved this problem by including the __autoload function at the top of my php file. So it looks like this:
<?php
require_once("path/to/include.inc");
//Needed for serialization/deserialization
function __autoload($class_name) {
include "path/to/". $class_name . '.php';
}
In PHP 5, this function isn't be needed but I was stuck until I used this function. Hope this helps someone else!
I know this is a really old question but I ran into this problem. After more research and experimenting I came up with a what I think is an acceptable alternative to storing classes in the session. It might be a bit hackish, but works for my current project.
NOTE: this work-around works for me because I start a session when a user logs in and don't want to include every possible class the user might, or might not encounter during the session. Including all the classes doesn't seem practical or efficient (but maybe this isn't any better ???).
First, my base class contains the following code that populates the object attributes from a given array, automatically.
class BaseClass {
public function __construct($properties=[]){
if (!empty($properties)) {
array_walk($properties, function ($val, $key) {
$this->fromArray($key, $val);
});
}
}
public function fromArray($property, $value){
return (property_exists($this, $property)) ? $this->$property = $value : null;
}
public function toArray(){
return get_object_vars($this);
}
}
The Work-Around:
I use the toArray() method to convert a class instance to an array before it goes into the session, then create a new instance of the class when fetching it from the session.
$_SESSION['user'] = $userInstance->toArray();
// ... do stuff ...
$userInstance = new User($_SESSION['user']);
This is also really handy for writing classes to a database and converting to JSON. Both of which are made easier when working with a PHP array.
Like I said above, this may or may not be the most efficient way to handle this problem. It also raises the question, "should I be using PHP classes if I'm just going to convert to arrays?"
I run into the same problem and the solution was inspired by #bobince answer
To be able to do it you have to have defined the class in question before calling
session_start
First, my session was set like this:
$_SESSION["customer"] = $customerObj;
Then before calling the session_start(), I have to load or defined the class first by importing it and then call session_start() right after
require 'entity/Customer.php';
ob_start();
session_start();
$customer = new Customer();
if (isset($_SESSION["customer"]))
{
$customer = $_SESSION["customer"];
echo $customer->getCustomerName();
}
My mistake was sending the user to a PHP page without including the class in that page, only in the original page.
Looked something like this:
index.php
include __DIR__.'AirInfo.php';
session_start();
$plan = new Plan();
header('Location: session.php');
session.php
// Should have put include __DIR__.'AirInfo.php' here
session_start();
My mistake here was that I had set the session.auto_start setting to on. The session would then be initialized before any line of code (including the autoloader) will be called.
I have the same problem with Google Photo API When Try to Authenticate my app and Access Photo API.
Solve it by just use session_start() after include and all use statements.
Here my complete code:
include "./vendor/autoload.php";
use Google\Auth\Credentials\UserRefreshCredentials;
use Google\Photos\Library\V1\PhotosLibraryClient;
use Google\Photos\Library\V1\PhotosLibraryResourceFactory;
use Google\Auth\OAuth2;
session_start();
//rest of code comes here
Short version of #bobince's excellent answer, if you're using an MVC framework and a classmap or psr-4 autoloading etc...
[front controller]
//Do this before session start because session has an object that will not work
// if the class has not been loaded already
require_once('vendor/autoload.php');
//Start a session after your autoload
session_start();
You might just be calling,
session_start();
session_start();
twice in your code. Call it once. Check required php classes for repeats. This was the fix for me.
I have some PHP code similar to the following:
foreach ($settingsarray as $settingsfile)
{
include ($settingsfile);
// do stuff here
}
$settingsarray is an array of file names from a particular folder.
The problem is, that each $settingsfile defines constants (with the same names), which of course, can not be redefined.
What possible methods are there to prevent errors occurring in this situation?
Two options I can think of include, changing all the constants to variables and using PHP namespaces.
However, I'm not sure how I would go about using namespaces, it would require the declaration at the start of every $settingsfile? Is there a method of isolating constants, ariables and functions without using namespaces?
What I would really love, is to be able to do something as simple as:
foreach ($settingsarray as $settingsfile)
{
{//added braces to indicate where the isolation is
include ($settingsfile);
// do stuff here
}//what happens in here does not affect what happens outside of here
}
I should just note, this is part of a new feature, and is the only part of the code that loads all the $settingsfiles. The rest of the code only ever loads one file at a time. One reason I am using constants is so that I don't have to worry about defining variables "global" to be able to access them inside functions.
My answer is somewhat complex, but should work for you quite nicely. I'm assuming you have a ton of these settings files, since you're so averse to changing each one individually.
If you're able to use namespaces, I'll assume you've already upgraded to PHP 5.3RC2. Copy the following into a .php file, and change the namespace to your liking:
<?php
namespace myapp\config;
function define($key, $val) {
Config::set($key, $val);
}
class Config {
private $vars = array();
// This class should not be instantiated
private function __construct() {}
public function set($key, $val) {
self::$vars[$key] = $val;
}
public function get($key) {
return isset(self::$vars[$key]) ? self::$vars[$key] : NULL;
}
}
?>
Include that in your code, and now making everything work is a simple matter of changing the
<?php
in your settings files to
<?php namespace myapp\config;
If you have a ton of them, a quick 'sed' command in your terminal should take care of it quite nicely, all in one fell swoop.
If I lost you on how to use my code, here's an example:
<?php
require_once('the_php_file_i_just_gave_you.php');
use \myapp\config\Config;
foreach ($settingsarray as $settingsfile) {
include ($settingsfile);
$varname = 'key';
echo "In this file, '$varname' equals: " . Config::get($varname);
}
?>
Good luck!
Okay, so if I'm understanding you correctly, inside that loop you'd like to do things with the constants defined inside each $settingsfile and then basically get rid of them after each loop?
Unlike variables, a constant is a value that, once set, cannot be changed or unset during the execution of your script
src
Your only option is to change the define() declarations to variables.
not easy but you could read the php file with file_get_contents($settingsfile) then rewrite the constants, and eval the code.
$settingsstr = file_get_contents($settingsfile);
$settingsstr = preg_replace('/<\?\w*(.*)/', '\\1', $settingsstr);//get rid of the php open tag
$settingsstr = preg_replace('/define\("(\w+)"/', 'define("NAMESPACE_\\1"', $settingsstr);
eval($settingsstr);
You have a few options:
Use PHP Namespaces
Surprisingly, PHP actually does have support for namespaces. I was very surprised to find this since I have been working in PHP for years and have never heard of this feature. It is likely a newer feature and one that is not commonly used, so I would suggest not using this method
Use Class Constants
You could use classes and class constants to load your settings. Class constants are local to the defining class, so you will not need to worry about name collisions within the class. You would probably need to do more than include the file, but you could call a method on each class, such as defineConstants() or loadConfiguration() that would define the application constants, keeping the class constants for internal use.
Use Functions to Extend the Settings
Another idea is to create a settings array and separate functions to 'extend' those settings. That way you can over-write any configuration in subsequent functions without causing an error. Finally, if you want to put those in constants, you could loop over the array and use define() to define the constants.
There are many other ways to accomplish what you are trying to do, but these are just a few ideas.
A settings file should not contan PHP code.
It should be some kind of "standard" format.
http://www.php.net/parse_ini_file
In the first step (in your foreach) you read all files
Collect the variables into a temporary array
In another loop over this array, you can define them as constants.
If you routinely feel the need to "redefine constants" then something is amish in the application design.