Неизменяемые объекты в PHP?

Хорошо ли создавать объекты, которые нельзя изменить в PHP?

Например, объект даты, который имеет методы setter, но всегда будет возвращать новый экземпляр объекта (с измененной датой).

Будут ли эти объекты запутываться для других людей, которые используют класс, потому что в PHP вы обычно ожидаете, что объект изменится?

Пример

$obj = new Object(2);

$x = $obj->add(5); // 7
$y = $obj->add(2); // 4

Ответ 1

Неизменяемый объект не может быть изменен после его первоначального создания, поэтому использование методов setter не имеет смысла, поскольку оно противоречит этому базовому принципу.

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

Ответ 2

Неизменяемые объекты не имеют методов настройки. Период.

Каждый будет ожидать, что метод setXyz() будет иметь тип возврата void (или ничего не вернуть на свободно типизированных языках). Если вы добавите методы setter к вашему неизменяемому объекту, это смутит ад людей и приведет к уродливым ошибкам.

Ответ 3

На мой взгляд, объекты должны быть неизменными для объектов ценности. Кроме того, он не имеет больших преимуществ, если вы не делитесь своим объектом со всем своим приложением.

Здесь есть некоторые неправильные ответы, неизменяемый объект может иметь сеттеры. Вот некоторая реализация неизменяемых объектов в PHP.

Пример # 1.

class ImmutableValueObject
{
    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function getVal2()
    {
        return $this->val2;
    }
}

Как вы можете видеть после создания экземпляра, вы не можете изменить какое-либо значение.

Пример 2: с setters:

class ImmutableValueObject
{
    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function withVal1($val1)
    {
        $copy = clone $this;
        $copy->val1 = $val1;

        return $copy;    // here the trick: you return a new instance!
    }

    public function getVal2()
    {
        return $this->val2;
    }

    public function withVal2($val2)
    {
        $copy = clone $this;
        $copy->val2 = $val2;

        return $copy;
    }
}

Существует несколько вариантов реализации, и это ни в коем случае не является эксклюзивным списком. И помните, что с Reflection всегда есть способ обойти это в PHP, поэтому в конечном итоге неизменность все в вашей голове!

Также часто бывает хорошей практикой ставить неизменяемые объекты в качестве окончательных.

EDIT:

  • изменен setX для wiспасибо
  • добавил комментарий о финале

Ответ 4

Я сделал небольшую черту, избегая использования Reflection для облегчения реализации неизменяемости: https://github.com/jclaveau/php-immutable-trait

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

class ImmutableValueObject
{
    use JClaveau\Traits\Immutable;

    private $val1;
    private $val2;

    public function __construct($val1, $val2)
    {
        $this->val1 = $val1;
        $this->val2 = $val2;
    }

    public function getVal1()
    {
        return $this->val1;
    }

    public function withVal1($val1)
    {
        // Just add these lines at the really beginning of methods supporting
        // immutability ("setters" mostly)
        if ($this->callOnCloneIfImmutable($result))
            return $result;

        // Write your method body as if you weren't in an Immutable class
        $this->val1 = $val1;
        return $this;
    }

    public function getVal2()
    {
        return $this->val2;
    }

    public function withVal2($val2)
    {
        if ($this->callOnCloneIfImmutable($result))
            return $result;

        $this->val2 = $val2;

        return $this;
    }
}
  • Вы можете видеть, что здесь вы не возвращаете $ copy, а $ this, как заметил Константин К.
  • В нативном PHP https://secure.php.net/manual/en/class.datetimeimmutable.php есть мутаторы, которые будут возвращать новые экземпляры с примененной модификацией. Так что скопируйте вставные предложения о том, что неизменяемые объекты не должны иметь мутаторов, не кажется супер интересным.
  • Практика использования "withXXX" вместо "setXXX" очень интересна, спасибо за предложение! Я лично использовал "getsXXX" для настройки изменчивости экземпляра (дополнительный API в черте SwitchableMutability).

Надеюсь, что это может помочь некоторым людям здесь!

PS: предложения по этой маленькой функции действительно приветствуются :): https://github.com/jclaveau/php-immutable-trait/issues

Ответ 5

Из неизменяемого объекта вы можете получить его значения, но изменить их невозможно. Здесь вы можете увидеть пример неизменяемого класса:

<?php 

declare(strict_types=1);

final class Immutable 
{
    /** @var string */
    private $value;

    public static function withValue(string $value): self
    {
        return new self($value);
    }

    public function __construct(string $value) 
    {
        $this->value = $value;
    }

    public function value(): string 
    { 
        return $this->value;
    }
}

// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value(); 

Ответ 6

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

Многие вещи в мире разработки являются субъективными - наши подходы, методология и т.д. - но "неизменяемый" - довольно твердое определение:

"Неизменное":
- Не меняется с течением времени или не может быть изменен.

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

Если вам нужно вызвать объект и установить или изменить данные после его создания, это не является неизменным объектом.

Вы бы сняли с машины два колеса и назвали бы это мотоциклом?

Есть некоторые разговоры о методах для "неизменяемого" класса, который назван без слова "set", но это не останавливает их функциональность, являющуюся методом, который устанавливает данные. Вы можете назвать его thisDoesNotSetAnything(int $id) и разрешить передачу данных, в которых изменяется объект. Это будет сеттер, и, следовательно, объект изменчив.