我想创建一个类并扩展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
答案 0 :(得分: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
}
}
答案 1 :(得分:6)
我觉得您并不真正理解您正在使用的bitwise operators |
和&
。这样做是比较bitlevel上的输入和改变位。因此,要理解,您需要将所有值都放在其二进制格式中并进行检查。
当其中一个位为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->flags
包含16368
或11111111110000
。现在,如果只取右边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;
现在您可以在创建它时使用标记功能。因为只有实际使用构造函数设置了一个标志,它才会被接受。
答案 2 :(得分:2)
你的问题是这里的声明:
const CURRENT_AS_FILEINFO = 0
当定义一个值为零的位标志时,它永远不会出现在位掩码上。
答案 3 :(得分: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
毫无意义
答案 4 :(得分:0)
你试图以一种假定它们是2的幂的方式使用不是2的幂的东西。
您感到困惑的行可以用十六进制重写为
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是真的。您不能使用这些标志,就像您目前如何进行简单测试一样,确定它们是否有效。
我猜你应该只检查确定的确切标志,而不是任意数字。
修改强>
嗯 - 我明白你的意思了。似乎存在不兼容的标志,因为它们会发生冲突,例如。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。然而,它并不容易阅读。
我认为可能在该课程之外使用那些标志可能不是一个好主意。