Page 1 of 1

Resources as constants - An experiment

Posted: Sun Jul 16, 2006 10:02 am
by Verminox
I was experimenting with constants and while reading the Constantssection in the manual, I came across this line:
Only scalar data (boolean, integer, float and string) can be contained in constants. Do not define resource constants.
I wondered why it said “Do not define resource constants” rather than “You cannot define resource constants“. So I decided to try for myself, and as I did I noticed the following behaviour:

Code: Select all

<?php
error_reporting(E_ALL);
$file = ‘temp.txt’;
$h = fopen($file,‘wb’);
define( ‘FILE_HANDLE’ ,  $h );
$write = fwrite(FILE_HANDLE,‘Testing…’);
fclose(FILE_HANDLE);
echo $write; //10
?>
I was expecting this to throw an error of at least E_NOTICE if not E_WARNING telling me that what I did was incorrect, and that only scalars should be defined as constants. But I got nothing! In fact, the script ran smoothly and I got the result output as: 10, which was the (un)expected result referring to the number of bytes written by the fwrite() operation.

Pondering further why the manual would then state that only scalars should be defined as constants and not resources, I went on trying different things, such as defining the resource attained by mysql_connect() as a constant, and using it for queries, etc. and it always worked. So, I decided to bring this up in ##php on IRC and a member named dools told me the reason.

Constants, by definition, are constant, i.e. They cannot be redefined or modified in any way once they are defined. By this very definition, you may use the constant for what it was defined as, but not do anything to it. And therefore, I understood why the manual tells us not to define resource constants, by trying out what dools told me to do…

Code: Select all

<?php
error_reporting(E_ALL);
$file = ‘temp.txt’;
$h = fopen($file,‘rb’);
define( ‘FILE_HANDLE’ ,  $h );
fclose(FILE_HANDLE);
$read = fread(FILE_HANDLE,10);
echo $read; //Testing...
?>
Notice that fread() is after fclose(), which would mean that we expect an error of level E_WARNING since the stream resource has been closed and is no longer valid. However, this is not what happens. We get an output of ‘Testing...‘. This is because since a constant cannot be changed and/or modified, fclose() has no effect on it, and the resource will always contain the same value that it did when it was first defined.
  1. The manual does say that resource constants should not be defined, however, it would have made things much easier if this was made a strict rule, or if it had at least thrown an error of level E_NOTICE stating that a resource has been defined as a constant. Perhaps this might be considered for future versions.
  2. The manual also states that:
    Constants may only evaluate to scalar values.
    However, that is not true for the above experiment. is_resource(FILE_HANDLE) returns true and a resource is in no way a scalar value.
  3. Thirdly, I noticed something strange with the behaviour of the file pointer as a constant. Just the mere act of defining the resource constant affects the rest of the script. I tried the following:

    Code: Select all

    <?php
    error_reporting(E_ALL);
    $file = ‘temp.txt’;
    $h = fopen($file,‘rb’);
    define( ‘FILE_HANDLE’ ,  $h );
    fclose($h);
    $read = fread($h,10);
    echo $read; //Testing...
    ?>
    And it still read the file correctly! Notice that this time I have used the variable $h for fclose() and fread() instead of the constant, in which case it should have caused the resource to close, but it does not. The constant FILE_HANDLE is not used at all in the script, but just defining it causes this strange behaviour. If I were to remove the line that defined the constant and leave everything else intact, it would throw the error saying that the resource stream used for fread() is invalid.
Note: Similar behaviour was observed with mysql_connect() and using mysql_close(), where the connection did not close because it was defined as constant.

Posted: Sun Jul 16, 2006 10:48 am
by Chris Corbyn
Hmm, we've used Constants to hold DB handles before :?

Code: Select all

$this->db->query($query, DB_FOO);

$result = $this->db->query($query, DB_BAR);
Worked no probs.... I thought it was only the *resource ID* that was returned.

It's better to use a registry to hold the connections though.

Code: Select all

$this->db->foo->query($query);

$result = $this->db->bar->query($query);

Posted: Sun Jul 16, 2006 11:05 am
by sweatje
d11wtq wrote:Hmm, we've used Constants to hold DB handles before :?
Works great for global access, but whoe is you is you accidentally

Code: Select all

mysql_close(DB_FOO);
8O while other portions of the script still need it

Posted: Sun Jul 16, 2006 12:02 pm
by Verminox
Yes constants can be used to hold DB handles, but shouldn't... as the manual says and as I demonstrated above.

For example, if you tried something like this:

Code: Select all

<?php

$foo = mysql_connect($host1, $username ,$password);
$bar = mysql_connect($host2, $username, $password);
define('FOO', $foo);
define('BAR', $bar);

mysql_select_db('db1',FOO);
mysql_select_db('db2',BAR);


mysql_query($query); //Uses BAR as the last open connection

//All works upto now

mysql_close(BAR); //Appears that we have closed BAR

mysql_query($query); //Still uses BAR

?>

The last mysql_query() appears to use FOO, since we have closed BAR by mysql_close(), but that is not what happens. BAR is still used because the constant cannot be modified and hence the connection has not been closed.


Edit: I tried passing $foo as &$foo and $bar as &$bar but it doesnt change anything. And to those who are thinking that the last mysql_query() is using the variable $bar and not the constant BAR, I even tried with passing the second argument as BAR, and it still worked, proving that mysql_close(BAR) does not work.


Similar behavior will be observed with fclose() as shown in my first post. If you want to close a file to save memory, you cannot do that with constant file handles.

Posted: Sun Jul 16, 2006 12:14 pm
by Chris Corbyn
sweatje wrote:
d11wtq wrote:Hmm, we've used Constants to hold DB handles before :?
Works great for global access, but whoe is you is you accidentally

Code: Select all

mysql_close(DB_FOO);
8O while other portions of the script still need it
:lol: Looking at ~Verminox's post that unfortunate issue could never happen :)

Out of curiosity, what's to stop that happening with storing DB connections in a registry?

Code: Select all

class dbRegistry
{
    //usual __get etc to retrieve objects from a static container
}

dbRegsitry::add(new db($host1, $user1, $pass1), 'foo');
dbRegsitry::add(new db($host2, $user2, $pass2), 'bar');

//SNIP....

class myClass
{
    protected $db;
    
    public function __construct()
    {
        $this->db = new dbRegistry;
    }
    
    public function doSomething()
    {
        $this->db->foo->query($query);
        $this->db->foo->close();
    }

    public function doSomethingElse()
    {
        $this->db->foo->query($query); //What, someone disconnected?
    }
}

$obj = new myClass;
$obj->doSomething();
$obj->doSomethingElse();

Posted: Sun Jul 16, 2006 12:18 pm
by Verminox
I don't really know what the class 'dbRegistry' does... does it make 'foo' and 'bar' static properties in this case?

If yes, then I assume they will not close.

Posted: Sun Jul 16, 2006 12:23 pm
by Chris Corbyn
Verminox wrote:I don't really know what the class 'dbRegistry' does... does it make 'foo' and 'bar' static properties in this case?

If yes, then I assume they will not close.
A registry example:

Code: Select all

class registry
{
    static public $registeredObjects = array();
    
    static public function add($object, $name)
    {
        self::$registeredObjects[$name] = $object;
    }
    
    static public function delete($name)
    {
        if (isset(self::$registeredObjects[$name])) unset(self::$registeredObjects[$name]);
    }
    
    //Magic trick!
    public function __get($name)
    {
        if (isset(self::$registeredObjects[$name])) return self::registeredObjects[$name];
        else return null;
    }
}
So 'foo'and 'bar' were just sitting in a static container yes. __get() passed them back to anything that came looking for them so if something changes in one, and something else requests the same object they'll get the changed version.

Posted: Sun Jul 16, 2006 12:35 pm
by Verminox
I see.

Well in that case this does not seem to come under the rules of the 'CONSTANT' data type in PHP, and so the problem should not occur with it. However, if you use class constants I assume this will happen.

On second thoughts, I don't think this is legal at all:

Code: Select all

<?php
class db {
	const foo = mysql_connect('localhost','username','password',true);
}
?>
Nor can we use: const foo = $foo; So the problem should not spill into OOP.

Posted: Sun Jul 16, 2006 12:40 pm
by Verminox
Ahh I made it spill into OOP as well :p

Code: Select all

<?php

$foo = mysql_connect('localhost','username','password',true);
define('foo', $foo);

class db {
	const bar = foo;
}


mysql_select_db('database',db::bar);

mysql_close(db::bar); //Doesn't close

$result = mysql_query('SELECT `id` FROM `ltbl`;',db::bar); //Queries right

echo mysql_num_rows($result); //Works

?>