Как использовать флаги функций?

Я хочу создать класс и расширить 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

Ответ 1

О умолчаниях и масках

В 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? Вам придется пересмотреть логику.

О взаимоисключающих флажках

Есть несколько флагов, которые нельзя использовать вместе, поскольку они приведут к поведению undefined; например:

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
    }
}

Ответ 2

У меня возникает ощущение, что вы не понимаете побитовые операторы | и &, которые вы используете. То, что это делает, - это сравнение ввода на 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 и все разные. Таким образом, каждый флаг будет соответствовать одной позиции в двоичном формате и, следовательно, имеет свои настройки yes/no.

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; 

Теперь вы можете использовать свою функцию флага, как вы ее создали. Поскольку только если флаг был установлен с конструктором, он будет принят.

Ответ 3

Ваша проблема заключается в следующем:

 const CURRENT_AS_FILEINFO  = 0 

При определении флага бит с нулевым значением он никогда не может отображаться на битовой маске.

Ответ 4

Просто используйте:

(($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 не имеет смысла

Ответ 5

Вы пытаетесь использовать вещи, которые не являются способностями двух, которые предполагают, что они являются двумя.

Строки, которые вы путаете, могут быть переписаны в шестнадцатеричном виде как

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. Однако это не очень легко читать.

Я думаю, что, возможно, использование этих флагов вне этого класса может просто не быть отличной идеей.