2015-01-23 6 views
4

У меня есть класс Rule, и я собираюсь создать класс RuleContainer, который фактически представляет массив объектов Rule.PHP-массив с только объектами определенного класса

Интересно, есть ли альтернатива создания нового класса. Есть ли (современный) подход к решению этой проблемы? То есть, что-то вроде использования SPL для определения массива, который позволяет добавлять объекты определенного класса.

Если нет, то какой интерфейс следует реализовать в моем классе RuleContainer?

ответ

2

Самый подходящий класс для вашей задачи - SplObjectStorage, но он не позволяет использовать тип typehint.

Я думаю, вы могли бы сделать следующим образом:

class RuleContainer extends SplObjectStorage 
{ 
    function attach(Rule $rule) 
    { 
     parent::attach($rule); 
    } 

    function detach(Rule $rule) 
    { 
     parent::detach($rule); 
    } 
} 

и так далее. Вы можете прочитать для интерфейса SplObjectStorage по адресу php.net и решить, что вы будете использовать и что нужно переопределить.

+0

Это именно то, что я искал! Спасибо за ваш ответ. – liopic

+0

После некоторых экспериментов я обнаружил, что подписи 'attach()' и 'detach()' НЕ могут быть изменены, поэтому это должно быть 'attach ($ rule, $ inf = null)' ... но, конечно, вы можете сделать проверка типа внутри метода. – liopic

1

В вашем случае, я бы реализовать интерфейс Iterator в RuleContainer, как я сделал несколько раз, когда мне нужно было что-то вроде Collection<T>, как мы знаем из других (типизированных) языков. И в методе add(Rule $item) или addItem(Rule $item) я бы удостоверился в определении типа аргумента (или используя instanceof), что элемент, который нужно добавить, имеет тип Rule.

1

В зависимости от характера использования для вашего класса контейнера, необходимо реализовать один или несколько из этих интерфейсов:

  • Iterator - использовать его в качестве foreach($container as $key => $value);
  • Countable - для count($container);
  • ArrayAccess - для $container[$key] (установите его, проверьте, isset(), unset());
+0

Очень полезный ответ, спасибо! – liopic

1

Использование PHP массивов рутин интерфейсов

Вы можете достичь своей цели с, например, ArrayAccess реализации. Вместе с Iterator он будет выглядеть следующим образом:

class ArrayStorage implements Iterator, ArrayAccess 
{ 
    private $holder = []; 

    private $instanceName; 

    public function __construct($instanceName) 
    { 
     if (!class_exists($instanceName)) { 
      throw new \Exception('Class '.$instanceName.' was not found'); 
     } 
     $this->instanceName = $instanceName; 
    } 
    public function rewind() 
    { 
     reset($this->holder); 
    } 

    public function current() 
    { 
     return current($this->holder); 
    } 

    public function key() 
    { 
     return key($this->holder); 
    } 

    public function next() 
    { 
     next($this->holder); 
    } 

    public function valid() 
    { 
     return false !== $this->current(); 
    } 

    public function offsetSet($offset, $value) 
    { 
     if (!($value instanceof $this->instanceName)) { 
      throw new \Exception('Storage allows only '.$this->instanceName.' instances'); 
     } 
     if (is_null($offset)) { 
      $this->holder[] = $value; 
     } else { 
      $this->holder[$offset] = $value; 
     } 
    } 

    public function offsetExists($offset) 
    { 
     return isset($this->holder[$offset]); 
    } 

    public function offsetUnset($offset) 
    { 
     unset($this->holder[$offset]); 
    } 

    public function offsetGet($offset) 
    { 
     return isset($this->holder[$offset]) ? $this->holder[$offset] : null; 
    } 
} 

Procs

Так что - да, вы делаете instanceof проверку в явном виде, но конечный пользователь вашего класса не знает об этом. Это возможно только для действительных экземпляров в контексте этого хранилища (вы можете проверить this fiddle для примера использования). Концепция, как:

$storage = new ArrayStorage('Foo'); //define what we will accept 
$storage[] = new Foo; //fine, [] array-writing 
$storage['baz'] = new Foo; //fine, key set 
foreach ($storage as $key => $value) { 
    echo($key. ' => '.PHP_EOL.var_export($value, 1).PHP_EOL); 
} 
//invalid, will not pass. Either throw exception or just ignore: 
$storage['bee'] = new Bar; 

End поведение отказоустойчивость проверка до вас, но, мое мнение, выбрасывая исключение является лучшим выбором здесь, поскольку они catchable, таким образом, конечный пользователь может решить, что делать в этом случае , Дополнительным вариантом может быть добавление Countable к хранилищу, но это не изменит общую идею.

И минусы

Даунсайд - нет, вы не сможете "typehint" это как-то. Хотя это полезно, в блоках doc вам все равно нужно будет указать, какую сущность вы принимаете. С точки зрения общих особенностей языка, существует arrayof RFC, Джо Уоткинс, который был предложен для PHP версии 5.6, но, к сожалению, не удалось. Может быть, это будет пересмотрено в версиях следующих версий.

+0

Да, вы можете «набрать шрифт» (со всем уважением) ;-) Использование класса как типа аргумента '$ value' в методе' offsetSet() '. К сожалению, это работает только с объектами и массивами, но все же ... http://codepad.viper-7.com/ESEr8B – Havelock

+1

В 'offsetSet()' - да, вы можете, но это бессмысленно для конечных пользователей (они даже не знаю о 'offsetSet()', они просто используют хранилище, как есть). Я рассказывал о том, что если какой-то метод ожидает массив точно таких же экземпляров - невозможно ввести тип с этой реализацией (в typehint будет 'ArrayStorage $ x' - и нет информации о том, что внутри) –

0

Вы можете сделать RuleContainer себя (как вы говорите) и делать всякие хитрости, чтобы вручную выполнять его, но вы живете в реальном мире я живу в реальном мире, и вы просто не нужен объект-контейнер для это, просто используйте массив.

Если ваша проблема просто одна из enforcement типа субъекта объекта А ЛЯ List<className>()вы не можете сделать это в PHP и быть честными, это дискуссионное использование в языках, где он нашел (я знаю, что будет проголосовать за это, но я по-прежнему буду прав) // за исключением этого помогает уточнить цель списка. // Честно говоря, мои 20+ лет программирования на почти всех языках (кроме машинного кода perl и fortran), я могу рассказать вам о таких конструкциях и просто не стоит накладных расходов людей, и они могут включать косвенные непреднамеренные нагрузки по их фактической стоимости:

Легкий компромисс: не лень, не начинают называть массив больше, чем что-то вроде tmpList и если вы absolultey определены implment простой test к http://php.net/manual/en/function.get-class.php в начале forloops вы наверняка в конечном итоге использовать

+0

Дело в том, что во-первых, я что я не мог сделать в PHP. Я работаю с PHP более 10 лет и постоянно использую чистые массивы. Но в последнее время я задавался вопросом, есть ли лучший способ использовать любые функции> 5.3. Похоже, вы можете использовать SplObjetcStorage для этого (как говорится в принятом ответе). В любом случае, спасибо. – liopic

+0

Да, это по-прежнему принцип «контейнера», хотя (вы можете свернуть свой с очень небольшим количеством хлопот). Я все еще думаю, что писать собственный '.Add()' с 'get_class()' -> 'Exception' может быть сделано так же легко, как набирать 'extend' на объекте, о котором разработчик PHP не слышал .. но там u go, возвращается к моей точке непредвиденных последствий .. –

+0

NB: PHP - JIT - вы можете использовать« очень действительное решение » 'просто выбросить исключение в forloop тоже. помните, что PHP никогда не является «скомпилированным» языком, это компилируемый язык JIT. т. е. нет среднего человека. Если ошибка возникла в цикле, она «все еще» произойдет во время выполнения, как это было бы при компиляции в (скажем) C# .., поскольку это различие отсутствует, вы (и другие разработчики) в равной степени выиграли бы от «исключения» в цикле for. –

Смежные вопросы