Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use function flags?

Tags:

php

I want to create a class and extend a PHP class FilesystemIterator as in the following code. I define a method hasFlag() and test whether it contains a flag (I want it to be like some other PHP functions, eg. glob), but the result is always different from expected. So how can I fix this problem?

class c extends FilesystemIterator 
{
    /* These are parent constants */
    const CURRENT_AS_FILEINFO  = 0 ;
    const KEY_AS_PATHNAME      = 0 ;
    const CURRENT_AS_SELF      = 16 ;
    const CURRENT_AS_PATHNAME  = 32 ;
    const CURRENT_MODE_MASK    = 240 ;
    const KEY_AS_FILENAME      = 256 ;
    const NEW_CURRENT_AND_KEY  = 256 ;
    const FOLLOW_SYMLINKS      = 512 ;
    const KEY_MODE_MASK        = 3840 ;
    const SKIP_DOTS            = 4096 ;
    const UNIX_PATHS           = 8192 ;

    public function __construct($flags) {
        $this->flags = $flags;
    }
    public function hasFlag($flag) {
        //How do I test $this->flags it contains a $flag???
        return ($this->flags & $flag) ? true : false;
    }
}

$c = new c(
    c::CURRENT_AS_FILEINFO | 
    c::KEY_AS_PATHNAME | 
    c::CURRENT_AS_SELF |
    c::CURRENT_AS_PATHNAME |
    c::CURRENT_MODE_MASK |
    c::KEY_AS_FILENAME  |
    c::NEW_CURRENT_AND_KEY |
    c::FOLLOW_SYMLINKS |
    c::KEY_MODE_MASK |
    c::SKIP_DOTS |
    c::UNIX_PATHS
);

var_dump($c->hasFlag(c::CURRENT_AS_FILEINFO));

EDIT 1

why is so ??

var_dump( ((0 | 16 | 32 | 240 | 3840) & 0)    == 0 );    //true
var_dump( ((0 | 16 | 32 | 240 | 3840) & 32)   == 32 );   //true
var_dump( ((0 | 16 | 32 | 240 | 3840) & 240)  == 240 );  //true
var_dump( ((0 | 16 | 32 | 240 | 3840) & 1024) == 1024 ); //true??
var_dump( ((0 | 16 | 32 | 240 | 3840) & 2048) == 2048 ); //true??
var_dump( ((0 | 16 | 32 | 240 | 3840) & 3840) == 3840 ); //true
like image 248
Jasper Avatar asked May 04 '13 14:05

Jasper


2 Answers

About defaults and masks

There are a few special flags used in FilesystemIterator, known as masks; they group together multiple flags that are related (or in this case, mutually exclusive) and should not be passed as regular flags; below is their binary representation:

000x00xx0000
+--++--+
   |   |
   |   +---- CURRENT_MODE_MASK
   |
   +-------- KEY_MODE_MASK

Those flags are used to determine whether the default key() and current() methods should be used. The defaults for both methods are defined here:

const CURRENT_AS_FILEINFO  = 0 ;
const KEY_AS_PATHNAME      = 0 ;

The following code illustrates how to test for it:

if ($flags & CURRENT_MODE_MASK == 0) {
    // CURRENT_AS_FILEINFO is used
} else {
    // either CURRENT_AS_PATHNAME, CURRENT_AS_SELF or CURRENT_AS_PATHNAME is used
}

if ($flags & KEY_MODE_MASK == 0) {
    // KEY_AS_PATHNAME is used
} else {
    // KEY_AS_FILENAME is used
}

The problem with having a function such as ->hasFlag() is that you can't differentiate between the two defaults, i.e. do you want to test CURRENT_AS_FILEINFO or KEY_AS_PATHNAME? You would have to rethink the logic.

About mutually exclusive flags

There are a few flags that can't be used together as they would result in undefined behaviour; for example:

const CURRENT_AS_SELF      = 16 ;
const CURRENT_AS_PATHNAME  = 32 ;

You can't define two types of behaviour for current(), either one of them (or the default) should be used. A compatible set of flags could be this:

$c = new c(
    c::CURRENT_AS_SELF |
    c::KEY_AS_FILENAME  |
    c::FOLLOW_SYMLINKS |
    c::SKIP_DOTS |
    c::UNIX_PATHS
);

About extending classes

Assuming that your constructor is the same as the parent you can remove your constructor altogether:

class c extends FilesystemIterator 
{
    public function hasFlag($flag)
    {
        $flags = $this->getFlags(); // use parent function here
        // logic here
    }
}
like image 187
Ja͢ck Avatar answered Oct 13 '22 01:10

Ja͢ck


I get the feeling that you dont realy understand the bitwise operators | and & that you are using. What this does is comparing input on bitlevel and changing the bits. So to understand, you need to put all the values in their binary format and check it. The | operator will set a bit to 1 when either one of the bits is a 1, and the & will set it to 1 when both bits are 1.

Take the numbers 2 and 4. Using |

2: 0010
4: 0100
--------
6: 0110

Using &

2: 0010
4: 0100
--------
0: 0000

So in your constructor, you are just adding all the numbers together and $this->flags will contain a single integer

c::CURRENT_AS_FILEINFO | 
c::KEY_AS_PATHNAME | 
c::CURRENT_AS_SELF |
c::CURRENT_AS_PATHNAME |
c::CURRENT_MODE_MASK |
c::KEY_AS_FILENAME  |
c::NEW_CURRENT_AND_KEY |
c::FOLLOW_SYMLINKS |
c::KEY_MODE_MASK |
c::SKIP_DOTS |
c::UNIX_PATHS

will translate to

0    : 00000000000000
0    : 00000000000000
16   : 00000000010000
240  : 00000011110000  //notice this one. It doesnt change 1 bit, but multiple.
256  : 00000100000000
256  : 00000100000000
512  : 00001000000000
3840 : 00111100000000  //notice this one. It doesnt change 1 bit, but multiple.
4096 : 01000000000000
8192 : 10000000000000
---------------------
16368: 11111111110000

So your $this->flags contains 16368.

Now for your var_dump test, I'll leave out all the exact bits, but you are making something like:

var_dump( ((0 | 16 | 32 | 240 | 3840) & 0)    == 0 );    //true
var_dump( ((4080) & 0)    == 0 );    //true

4080: 111111110000
0   : 000000000000
------------------ &
0   : 000000000000 //nowhere are both bits a 1 so the output is 0

So in result your var_dump statesment turn into:

var_dump( (4080 & 0)    == 0 );
var_dump( (4080 & 32)   == 32 );
var_dump( (4080 & 240)  == 240 );
var_dump( (4080 & 1024) == 1024 );
var_dump( (4080 & 2048) == 2048 );
var_dump( (4080 & 3840) == 3840 );

    //which is also..
var_dump( 0    == 0 );
var_dump( 32   == 32 );
var_dump( 240  == 240 );
var_dump( 1024 == 1024 );
var_dump( 2048 == 2048 );
var_dump( 3840 == 3840 );

    //which obvisouly is true on all accounts.

Now back to your hasFlag function. Remember that your $this->flags contains 16368 or 11111111110000. Now if you take only the bits on the right 10000 you will have the number 16. If you add a 1 to the left and change all the other bits to 0, you would get 100000000000000 which translates to 16384.

The result: Any number between 16 and 16384, converted to binary, will have atleast a single 1 in it on the same spot your flag has a 1 in it. So return ($this->flags & $flag) will be true for all those numbers.

Since you cannot change your flags as they are created in the parent, you need a different approach to check if its true. In this case, you need to make sure that the outcome of $this->flags & $flag matches the flag. Because only then the result is true So it would become

return ($this->flags & $flag) == $flag;

Just for reference:

If you could set the flags yourself and there weren't any composite flags, you could make all your flags a power of 2 and all different. This way every flag will correspond to a single position in the binary format and thus has its on yes/no settings.

const CURRENT_AS_FILEINFO  = 2 ;
const KEY_AS_PATHNAME      = 4 ;
const CURRENT_AS_SELF      = 8 ;
const CURRENT_AS_PATHNAME  = 16 ;
const CURRENT_MODE_MASK    = 32 ;
const KEY_AS_FILENAME      = 64 ;
const NEW_CURRENT_AND_KEY  = 128 ;
const FOLLOW_SYMLINKS      = 256 ;
const KEY_MODE_MASK        = 512 ;
const SKIP_DOTS            = 1024 ;
const UNIX_PATHS           = 2048 ;

Or in binary form so you can see each bit has its own position (note you could have started with 1, 2, 4 , etc to also use the first bit on the right):

const CURRENT_AS_FILEINFO   = 000000000010; 
const KEY_AS_PATHNAME       = 000000000100; 
const CURRENT_AS_SELF       = 000000001000; 
const CURRENT_AS_PATHNAME   = 000000010000; 
const CURRENT_MODE_MASK     = 000000100000; 
const KEY_AS_FILENAME       = 000001000000; 
const NEW_CURRENT_AND_KEY   = 000010000000; 
const FOLLOW_SYMLINKS       = 000100000000; 
const KEY_MODE_MASK         = 001000000000; 
const SKIP_DOTS             = 010000000000; 
const UNIX_PATHS            = 100000000000; 

Now you can use your flag function as you have created it. Because only if a flag has actually been set with the constructor, it will be accepted.

like image 23
Hugo Delsing Avatar answered Oct 13 '22 01:10

Hugo Delsing