Search
Write a publication
Pull to refresh
3
0
Евгений @CrazyNiger

Web Разработчик

Send message
Нет, нужно не часто, рассматривался гипотетический случай. А выше на меня «наехали», сказав что указание типа через нотации — это выкрутасы =)
А вот это зачет, возьму себе в копилку.
От этого можно защитится установив в конструкторе защиту от переопределения.
Интерфейс это вариант, при условии что оба класса его реализуют. А разделение на методы — это вынос проблемы на уровень выше, вызывающему коду теперь нужно принимать решение о том, какой метод вызвать.
К сожалению, из-за особенностей типизации PHP, статистически анализаторы много-где не могут помочь. Например, отсутствие возможности перегрузки методов вынуждает отказываться даже от typehintig-га.

public void method (ClassA a) {
// ...
} 
public void method (ClassB a) {
// ...
} 



в PHP превращается в
public method ($a) {
// работаем без поддержки анализатора =(
} 

Три причины:
1. Использование рефликсия является самым очевидным способом, обойти интерфейс и запихать в приватное поле все что вздумается, а в исходном комменте заявлено несколько способов.
2. Я точно знаю как «сломать» коллекцию через рефликсию, хочется увидеть что-то новое, чего я не знаю.
3. Я не очень хорошо знаком с возможностями рефлексии в других языках, но у верен, что в C# можно с ее помощью сломать даже местные дженерики.
Собственно, выше я писал вариант для этого. Но суть «спора» в том, что я рассуждаю с позиции того, что сейчас есть в языке (собственно, я так и написал в моей первом комменте).
В пыхе достаточно способов напихать в вашу коллекцию что попало

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

Т.е. делает тоже самое, что и указание джинерика в сигнатуре метода.
Пока в ПХП такая типизация, какая она есть, без костылей с аннотациями, к сожалению не обойтись, но кажется я вас понял. Если бы дженерики были нативные, то приведенный ниже код гарантировал бы, что внутри функции useCollection все элементы $posts являются экземплярами класса Post (если это не так, то будет эксешпшен при вызове).
function useCollection(Collection<Post> $posts) {
    // ....
}


Мой код позволяет добиться такого поведения, без особых заморочек:
function useCollection(Collection $posts) {
    if (!$posts->isInstanceOf(Post::class)) {
        throw new InvalidArgumentException();
    }
    /** @var Post[] $posts */
    // ....
}

Это 100% гарантирует, что каждый элемент коллекции является экземпляром Post

Дело вот в чем:
abstract public mixed offsetGet ( mixed $offset )


Никакие решения на базе mixed не позволят избавить клиентский код от необходимости проверок типа элементов коллекции.


Если вы внимательно посмотрите в мою реализацию метода offsetGet, то увидите, что в коллекцию могут быть добавлены только объекты того класса, который был указан при создании коллекции, так что необходимости проверять каждый элемент коллекции перед использованием нет.
Тогда везде в клиентском коде нужно проверять тип элементов в коллекции. Это лишь немногим удобней обычного массива в пхп


Похоже мы с вами друг-друга не понимаем. Будьте добры, приведите пример, как вы будите использовать джинерики, для отличия
$collection = new Collection<Post>();

от
$collection = new Collection<User>();
Для различия содержимого достаточно добавить в класс коллекции пару методов:
    /**
     * Замена для instanceOf
     * @param $class
     * @return bool
     */
    public function isInstanceOf($class)
    {
        return is_a($this->className, $class, true);
    }

    /**
     * @return string
     */
    public function getClassName()
    {
        return $this->className;
    }
Плодить подтипы не нужно. В клиентском коде достаточно указать имя класса в конструкторе класса-коллекции.

$collection = new GenericCollection<Post>(); //Вариант из статьи
$collection = new Collection('Post'); //Мой вариант

Обе строчки «равнозначны».
Реализовать дженерики в PHP можно проще простого, достаточно чу-чуть изменить конструктор и метод offsetSet:
class Collection implements Iterator, ArrayAccess {
//    ...

    /** @var string */
    private $className = null;

    /**
     * @param string $T
     */
    public function __construct($T) {
        if (is_string($T) && class_exists($T)) {
            $this->className = $T;
        } else {
            throw new \InvalidArgumentException("T must be name of class.");
        }
        $this->position = 0;
    }

//...
    public function offsetSet($offset, $value) {
        if (!($value instanceof $this->className)) {
            throw new \InvalidArgumentException("value must be instance of {$this->className}.");
        }
        if (is_null($offset)) {
            $this->array[] = $value;
        } else {
            $this->array[$offset] = $value;
        }
    }
}


$postCollection = new Collection(Post::class);

$postCollection[] = new Post(); //ok
$postCollection[] = 'Post'; //throw Exception


Единственное чего не хватает, так это дополнения в IDE. Но и как выше говорили, достаточно нормально следить за заполнением массива.

UPD: Если указать тип для $postCollection через PHPDoc как массив постов, то будет подсказывать
/** @var Post[] $postCollection */
$postCollection = new Collection(Post::class);
Ваши цены указаны без НДС, а счета клиентам вы выставляете с НДС или без?
by3 объявлен через const, потенциальные «другие места» заканчиваются в конце блока на пять строчек.
А что проверяем-то? То что хром умеет к переменной 50 прибавлять?
Наверно вот такой код имелся ввиду:

function kissMiss(){
   let startTime = new Date();
  for(let i = 1, t=50; i<=100; i++, t+=50) {
    setTimeout(()=>{
		let  s = '';
		s = !(i%3) ? 'Miss': '';
		s += !(i%5) ? 'Kiss': '';
		s = s ? s : i;
		console.log((new Date() - startTime) + ' >> ' + t +' >> '+s);		
	},t);
  }
}


Но по большому счеты, эксперимент показывает что setTimeout вполне подходит, у меня в хроме ровно 50мс между вызовами, хотя до первого чуть-больше.
На мой взгляд самый красивый лаконичный вариант =)
Хм… был уверен, что при таком коде во всех вызовах i=101, но как оказалось, если i объявлен через let, а не var, то замыкается его текущее значение.

Information

Rating
Does not participate
Location
Екатеринбург, Свердловская обл., Россия
Date of birth
Registered
Activity