Статьи

Клонирование в PHP

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

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

По значению, по ссылке или по обработчику?

Примитивные структуры данных PHP, такие как все скаляры (целые числа, строки, логические значения) и массивы, передаются по значению между функциями и методами. Это не означает, что они копируются каждый раз в новую память — система копирования при записи — но концептуально вы можете вести себя так, как будто это правда. Таким образом, вам никогда не потребуется клонировать скаляр или массив скаляров, поскольку они будут переданы за пределы текущей области только с копией, которая не может повлиять на исходное значение.

Есть два исключения из этого механизма. Когда вам нужен клон для немедленного * уничтожения вычислений, нет необходимости в специальных структурах:

public function myMethod()
{
    $values = $this->arrayOfIntegers;
    $values[] = rand(1, 42);
    return $values; // $this->arrayOfIntegers is still the same for the next round
}

Более того, некоторые нативные функции принимают параметры только по ссылке:

$values = $this->arrayOfIntegers;
sort($values);

И как таковые, эти функции должны быть переданы копии исходных структур данных, если вы не хотите, чтобы они были изменены. Вы можете узнать, имеет ли функция PHP доступ к параметрам по ссылке в ее документации, где они будут указаны с помощью & :

int preg_match ( string$pattern , string$subject [, array&$matches [, int $flags= 0 [, int $offset= 0 ]]])


ресурс
proc_open (
строка$cmd ,
массив$descriptorspec ,
массив&$pipes [,
строка$cwd [,
массив$env [,
массив$other_options ]]])

Но в отличие от sort () и его братьев и сестер, большинство этих функций полностью перезаписывают свои выходные аргументы, поэтому им никогда не передаются структуры данных, а вместо этого пустые массивы.

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

Клон ключевое слово

PHP имеет конструкцию для выполнения мелкого клонирования:

<?php
$object = new stdClass;
$object->collaborator = new stdClass;
$clone = clone $object;
var_dump($clone === $object);
var_dump($clone->collaborator === $object->collaborator);

$ php shallow_cloning.php
bool(false)
bool(true)

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

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

public function __clone()
{
    $this->collaborator = clone $this->collaborator;
}

Класс $ this- >laborator также может иметь метод __clone () и т. Д. Вплоть до остальной части графа объектов.

Этот механизм является дорогостоящим, но наиболее гибким, поскольку дает вам полный контроль над тем, что дублировать и чем делиться; например, вы можете решить полностью клонировать объект A и его соавтора B, но не соединение с базой данных, которое B содержит в качестве ссылки на поле.

Сериализация

Если вы просто хотите выполнить полное клонирование графа небольших объектов, сериализация — это самый быстрый механизм (с точки зрения программиста, а не из измерений производительности).

<?php
$object = new stdClass;
$object->collaborator = new stdClass;
$clone = unserialize(serialize($object));
var_dump($clone === $object);
var_dump($clone->collaborator === $object->collaborator);

$ php serialization.php
bool(false)
bool(false)

Сериализация преобразует граф объектов в строку и обратно в объекты PHP с теми же внутренними частными состояниями и ссылками на объекты, что и в исходных экземплярах. Он также имеет дело с циклами в небольшом объектном графе, но большие графы объектов не следует полностью клонировать с помощью этого метода: на этот раз мы действительно дублируем память, когда появляются новые экземпляры.

Существует также снижение производительности для сериализации для каждой выполняемой вами операции клонирования: убедитесь, что вы профилировали, чтобы убедиться, что вы не клонируете сам Zend Framework в каждом процессе PHP вашего приложения. Кроме того, некоторые объекты даже не могут быть сериализованы (например, соединения PDO).

Радикальный вывод

Наиболее радикальным решением проблемы клонирования является проектирование с неизменяемыми объектами значений:

class A
{
    private $b;
    public function __construct(B $b)
    {
        $this->b = $b;
    }
    // no other method modifies $b, which has the same structure.
}

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