10

我想创建一个类并扩展一个 PHP 类 FilesystemIterator,如下面的代码所示。我定义了一个方法 hasFlag() 并测试它是否包含一个标志(我希望它像其他一些 PHP 函数,例如 glob),但结果总是与预期不同。那么我该如何解决这个问题呢?

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

编辑 1

为什么会这样??

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
4

5 回答 5

11

关于默认值和掩码

中使用了一些特殊的标志FilesystemIterator,称为掩码;它们将多个相关的(或在这种情况下互斥的)标志组合在一起,不应作为常规标志传递;下面是它们的二进制表示:

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

这些标志用于确定是否应使用默认值key()和方法。current()两种方法的默认值都在这里定义:

const CURRENT_AS_FILEINFO  = 0 ;
const KEY_AS_PATHNAME      = 0 ;

以下代码说明了如何对其进行测试:

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
}

具有诸如此类功能的问题->hasFlag()是您无法区分两个默认值,即您要测试CURRENT_AS_FILEINFO还是要测试KEY_AS_PATHNAME?您将不得不重新考虑逻辑。

关于互斥标志

有一些标志不能一起使用,因为它们会导致未定义的行为;例如:

const CURRENT_AS_SELF      = 16 ;
const CURRENT_AS_PATHNAME  = 32 ;

您不能为 定义两种类型的行为current(),应该使用其中一种(或默认值)。一组兼容的标志可能是这样的:

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

关于扩展类

假设您的构造函数与父构造函数相同,您可以完全删除构造函数:

class c extends FilesystemIterator 
{
    public function hasFlag($flag)
    {
        $flags = $this->getFlags(); // use parent function here
        // logic here
    }
}
于 2013-05-04T14:50:34.837 回答
6

我觉得您并不真正了解按位运算符 |并且&您正在使用。这样做是在位级别上比较输入并更改位。所以要理解,你需要把所有的值都放在它们的二进制格式中并检查它。当|其中一个位为 1 时,运算符将一个位设置为 1,当两个位为 1 时,运算符将其设置&为 1。

取数字 2 和 4。使用|

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

使用&

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

因此,在您的构造函数中,您只需将所有数字相加,$this->flags并将包含一个整数

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

将转化为

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

所以你的$this->flags包含16368

现在为了您的var_dump测试,我将省略所有确切的部分,但您正在制作类似:

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

所以结果你的 var_dump 语句变成:

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.

现在回到你的hasFlag功能。请记住,您的$this->flagscontains1636811111111110000. 现在,如果你只取右边的位,10000你将得到数字 16。如果你在左边添加一个 1 并将所有其他位更改为 0,你将得到100000000000000转换为 16384。

结果: 16 到 16384 之间的任何数字,转换为二进制,在你的标志有 1 的同一点上至少有一个 1。所有这些数字都是如此return ($this->flags & $flag)

由于您无法更改在父项中创建的标志,因此您需要一种不同的方法来检查其是否为真。在这种情况下,您需要确保结果$this->flags & $flag与标志匹配。因为只有这样结果才是真的所以它会变成

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

仅供参考:

如果您可以自己设置标志并且没有任何复合标志,则可以将所有标志设为 2 的幂并且完全不同。这样,每个标志都将对应于二进制格式中的一个位置,因此有其是/否设置。

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 ;

或者以二进制形式,这样您就可以看到每个位都有自己的位置(请注意,您可以从 1、2、4 等开始,也可以使用右侧的第一位):

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; 

现在你可以使用你创建的标志函数了。因为只有在构造函数中实际设置了一个标志时,它才会被接受。

于 2013-05-13T14:11:50.000 回答
2

你的问题是这里的声明:

 const CURRENT_AS_FILEINFO  = 0 

当用零值定义你的位标志时,它永远不可能出现在位掩码上。

于 2013-05-04T14:09:14.887 回答
1

只需使用:

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

例子:

class Test
{
    /* These are parent constants */
    const FLAG_A = 1;  // binary 01
    const FLAG_B = 2;  // binary 10

    public function __construct($flags) {
        $this->flags = $flags;
    }   
    public function hasFlag($flag) {
        return (($this->flags & $flag) === $flag) ? true : false;
    }   
}


$t = new Test(Test::FLAG_A | Test::FLAG_B);
var_dump($t->hasFlag(Test::FLAG_A)); # bool(true)
var_dump($t->hasFlag(Test::FLAG_A)); # bool(true)

$t = new Test(Test::FLAG_A);
var_dump($t->hasFlag(Test::FLAG_A)); # bool(true)
var_dump($t->hasFlag(Test::FLAG_B)); # bool(false)

$t = new Test(Test::FLAG_B);
var_dump($t->hasFlag(Test::FLAG_A)); # bool(false)
var_dump($t->hasFlag(Test::FLAG_B)); # bool(true)

解释:

想象一下,这$flags是当前设置为的CURRENT_AS_PATHNAME | CURRENT_AS_SELF二进制表示(二进制表示缩短):

...110000

您现在尝试检查标志CURRENT_AS_SELF是否处于活动状态。CURRENT_AS_SELF在二进制表示中看起来像这样:

...010000

如果您现在应用logical AND运算符&,则只有在两个操作数中设置的位将在结果中设置。什么给你:

...010000

这与国旗相同。这就是为什么=== $flag

另请注意@mario 的答案,带有值的二进制标志0没有意义

于 2013-05-04T14:05:00.763 回答
0

您正在尝试以假设它们是二的幂的方式使用不是二的幂的东西。

您感到困惑的行可以用十六进制重写为

var_dump( ((0 | 0x10 | 0x20 | 0xf0 | 0xf00) & 0x400) == 0x400 ); //true??
var_dump( ((0 | 0x10 | 0x20 | 0xf0 | 0xf00) & 0x800) == 0x800 ); //true??

一旦你意识到:

0xf00 = (0x800 + 0x400 + 0x200 + 0x100)

很明显为什么 0xf00 & 0x400 == 0x400 是真的。你不能像现在这样使用这些标志来做简单的测试来判断它们是否有效。

我猜你应该只检查它们定义的确切标志,而不是任意数字。

编辑

嗯 - 我明白你的意思。似乎有些标志与 out 标志不兼容,因为它们发生冲突,例如

const KEY_AS_FILENAME      = 256 ;
const NEW_CURRENT_AND_KEY  = 256 ;

所以不能独立设置和测试——这很奇怪。

我已经尝试阅读 FileIterator 的源代码 - 它位于https://github.com/php/php-src/blob/master/ext/spl/spl_directory.c。但是,它不是很容易阅读。

我认为可能在该课程之外使用这些标志可能不是一个好主意。

于 2013-05-05T17:49:58.920 回答