Php удалить объект из памяти
Сегодня на работе разбирался с PHPшным сборщиком мусора. Обнаружилась одна жутко неприятная вещь, которая называется recursive reference memory leak - объекты с перекрестными ссылками не удаляются из памяти.
Как известно, объекты PHP 5 реализуются через механизм smart-pointer. Понимание этого механизма вообще нужно для адекватной работы с объектами, так что изложу вкратце. Осторожно, мнение выходца из C++. :)
Что такое smart-pointer? Это такой паттерн, который подсчитывает количество ссылок на себя, и когда оно становится равным нулю, удаляет объект, на который ссылается сам.
Подробнее про самостоятельную реализацию smart-pointer на PHP можно почитать, например, в Advanced PHP Programming.
- Когда создается объект, под него выделяется участок память и smart-pointer. Переменной присваивается ссылка на smart-pointer.
- При копировании объекта, на самом деле, создается еще одна ссылка на smart-pointer.
- При клонировании объекта оператором clone создается копия памяти и новый smart-pointer с одной ссылкой (той, что новая).
- При выполнении unset($object) удаляется только данная ссылка на объект - если это не последняя ссылка, то объект остается в памяти. (важно!)
- При выполнении $object->__destruct() выполняется деструктор объекта как метод - это тоже не освобождает память! Так же, как и object.~Class() в C++. Более того, это, возможно, испортит объект - а ссылки на него могли остаться.
- Единственный способ удалить объект из памяти - удалить все ссылки на него.
Конечно, обычно PHP очищает ссылки при выходе из области определения переменной, и память освобождается вовремя.
Поправка: память очистится не сразу, а при ближайшем запуске сборщика мусора.
Но есть одна ситуация, и довольно часто используемая, когда этого не происходит.
Удаление свойства $a->b происходит после удаления объекта $a (резонно - в деструкторе оно может пригодиться). В деструкторе B объект `$b->parent уже удален, и эта ссылка не очищается. Отсюда утечка памяти.
Пример из жизни
Привожу пример из жизни классической ActiveRecord-модели:
Так вот такой код со страшной силой жрет ОЗУ.
Решение
На данный момент приходится обнулять ссылки вручную:
[edit] кроме того, вызывать деструктор для A придется вручную - поскольку в ссылка на него остается во вложенном объекте, он не будет удален автоматически.
Если Вы когда-нибудь изучали PHP-код открытых проектов, то вы могли встречать методы, начинающиеся с двойного подчеркивания. Это и есть те самые магические методы, с помощью которых вы сможете определить поведение вашего объекта при различных манипуляциях с его экземпляром.
Предполагаю, что вы уже сталкивались с некоторыми из них, ведь существуют довольно распространенные методы, и тем не менее, я считаю, что компетентному программисту PHP необходимо уверенное владение всеми возможностями языка.
Я думаю, это можно считать, своего рода, отправной точкой в мир Магических методов.
Приступая к изучению
Когда я сам изучал этот материал, я использовал всевозможные учебники и статьи, в которых излагались довольно глупые или вообще бесполезные примеры. Я считаю, что для того чтобы понять что-то нужно попробовать это в контексте реальной задачи. Именно с этого мы и начнем.
Представим себе, что мы хотим получать все твиты, при помощи Tweeter Api. Мы получаем JSON всех твитов текущего пользователя и хотим превратить каждый твит в объект с методами, которые позволят проводить определенные операции.
Ниже, я представил базовый класс Tweet:
Теперь, когда мы создали объект, мы можем приступать к изучению самих методов. (Прим. переводчика — некоторые конструкции будут иногда опускаться, чтобы акцентировать на роли и возможностях каждого метода)
Конструкторы и Деструкторы
Пожалуй, одним из самых наиболее распространенных магических методов является конструктор ( __construct() ). Если вы достаточно внимательно следили за созданием приложения Cribbb в моем блоге, вы достаточно осведомлены об этом методе.
Метод __construct() автоматически вызывается, когда был создан экземпляр объекта. В нем вы можете задать начальные свойства объекта или установить зависимости.
Когда мы создаем экземпляр класса Tweet, мы можем передать параметры, которые поступят в метод __construct(). Из примера выше, вы можете видеть, что мы не вызываем этот метод и не должны вызывать — он вызывается автоматически.
Со временем у вас возникнет необходимость расширение класса путем его наследования. Иногда родительский класс так же имеет метод __construct(), который совершает определенные действия, таким образом чтобы не потерять функционал класса-родителя, нужно вызвать и его конструктор.
При попытке удалить объект будет вызван метод __destruct(). Опять же, по аналогии с конструктором — это не то что нужно вызывать, ведь PHP все сделает за вас. Этот метод позволит вам очистить все, что вы использовали в объекте, например соединение с базой данных.
Если быть честным, то большую часть метода __destruct(), изложенного выше я скрыл от вас. PHP на самом деле не из тех языков, где процесс будет существовать достаточно длительное время, так что я не думаю, что у вас будет что-либо для чего мог бы понадобиться деструктор. Сам по себе жизненный цикл запроса в PHP настолько мал, что от данного метода будет скорее больше хлопот, чем пользы.
Геттеры и сеттеры
Когда вы работаете с обьектами в PHP, вам бы очень хотелось обращаться к свойствам объекта как-то так:
Однако, если у свойства text установлен модификатор доступа protected, то такое обращение вызовет ошибку.
Магический метод __get() будет отлавливать обращения к любым не публичным свойствам.
Метод __get() приминает имя свойства, к которому вы обращаетесь, в качестве аргумента. В приведенном выше примере сначала проверяется существование свойства в объекте и если оно существует, то возвращается его значение.
Как и в примерах выше — вы не должны вызывать этот метод напрямую, PHP будет вызывать его каждый раз, при попытке получения доступа к не публичным свойствам класса.
В обратной ситуации — если вы попытаетесь установить значение свойства, которое не является публичным — вы получите ошибку. И опять же, в PHP есть свой метод, который будет вызван при попытке установить в не публичное поле какое-либо значение. Данный метод принимает 2 параметра в качестве аргументов — свойство, в которое хотели записать значение, и само значение.
Если вы хотите использовать данный метод, ваш класс получит свойство, на подобии этого:
В приведенных выше примерах я показал как можно получить или установить значения свойств, не имеющих модификатор доступа public. Однако работа с данными магическими методами не всегда будет лучшей идеей. Гораздо лучше иметь множество методов для получения и записи свойств, так как в этом случае они формируют определенный API и это означает, что при изменении способа хранения или обработки ваш код не будет сломан.
Впрочем, вы все равно иногда будете встречать методы __get() и __set(), которые принято называть геттерами и сеттерами соотвественно. Это довольно хорошее решение, если вы решили изменить какое-либо значение или добавить немножко бизнес-логики.
Проверка свойства на существование
Если вы знакомы с PHP, вы скорее всего знаете о существовании функции isset(), которую обычно применяют при работе с массивами. Вы так же можете использовать эту функцию, для того чтобы понять — задано свойство в обьекте или нет. Вы сможете определить магический метод __isset(), для того чтобы можно проверять не только общедоступные свойства, но и другие.
Как вы видите выше, __isset() метод отслеживает вызов функции на проверку существования и получает в качестве аргумента — название свойства. В свою очередь, в методе вы можете использовать функцию isset(), для проверки существования.
Очистка переменной
По аналогии с функцией isset(), функция unset() обычно используется при работе с массивами. Опять же, вы можете использовать функцию unset() для того чтобы очистить значение не публичного свойства. Чтобы применить данный метод на не публичные свойства, вам понадобиться метод __unset(), который будет отслеживать попытки очистить не публичный свойства класса.
Приведение к строке
Метод __toString() позволит вам определить логику работы вашего приложения, при попытке привести обьект к типу строке.
Например:
Можно сказать, что когда вы пытаетесь обратиться к обьекту, как к строке, например при использовании echo, обьект будет возвращен так, как вы определите в __toString() методе.
Хорошей иллюстрацией в данном случае может случить Eloquent Models из фреймворка Laravel. При попытке приведения обьекта к строке вы получите json. Если вы хотите увидеть как Laravel это делает, рекомендую обратиться к исходному коду.
Сон и пробуждение
Функция сериализации ( serialize() ), является довольно распространенным способом хранения обьекта. Например, если бы вы хотели сохранить обьект в базе данных, для начала вы должны были бы его сериализовать, затем сохранить, а когда бы он вам потребовался снова, вы должны были бы его получить и десериализовать ( unserialise() ).
Метод __sleep(), позволяет определить какие свойства должны быть сохранены. Если бы мы к примеру, не хотели сохранять какие-либо связи или внешние ресурсы.
Представим себе, что когда мы создаем обьект, мы хотим определить механизм его сохранения.
Когда мы готовим к сохранению обьект, нам естественно не нужно сохранить подключение к базе данных, ведь в будущем это будет бессмысленно.
Поэтому в методе __sleep() мы определим массив свойств, которые должны быть сохранены.
А после того как настанет время для пробуждения обьекта, нам могут понадобиться все то, что мы не сохранили при сериализации. В конкретном примере нам нужно установить соединение с базой данных. Это можно сделать, при помощи магического метода __wakeup().
Вызов методов
Магический метод __call(), будет перехватывать все попытки вызовов методов, не являющихся публичными. Например, у вас может быть массив данных, которые вы хотите изменить:
Еще один типичный пример это использование другого публичного API в своем обьекте.
В приведенном выше примере, мы можем вызвать метод getLocation на обьекте класса Tweet, но на самом деле мы его делегируем классу Location.
Если вы пытаетесь вызвать статический метод, вы можете так же воспользоваться __callStatic() магическим методом. Главное помните, что работает он лишь при вызове статичных методов.
Клонирование
Когда вы создаете копию объекта в PHP, по факту в переменную записывается не новый объект, а идентификатор, ссылающийся на оригинальный обьект. То есть любое изменение в ссылающимся объекте влечет за собой изменение первоначального объекта, однако удаление любого из объектов не повлияет на существование других:
Для того чтобы создать копию обьекта вам следует использовать ключевое слово clone.
Однако, если у нас есть несколько связанных обьектов, зависимости, которые находятся в них, так же будут скопированы.
Для того чтобы решить данную проблему мы можем определить метод __clone() для того чтобы определить правильное поведение:
Вызов обьекта как функции
Магический метод __invoke() позволяет определить логику работы обьекта, при попытке обратиться к обьекту как к функции.
В данном примере я применяю обьект $tweet, как callback-функцию, ко всем значениям массива $users. В данном примере, мы добавим твит каждому пользователю. Согласен, данный пример является немного искусственным, однако я уверен, что вы действительно найдете применение этому методу.
Заключение
Как вы могли уже убедиться, PHP использует магические методы для реагирования на те или иные действия с методами или свойствами обьекта. Каждый из данных методов срабатывает автоматически, вы просто определяете что должно произойти, а об остальном позаботиться PHP.
В течении довольно длительного времени я не понимал истинного значения магических методов. Я думал, что они нужны, лишь для того чтобы делать какие-либо интересные вещи с обьектами. И когда я, наконец, понял их истинное предназначение, я смог писать более мощные обьекты в рамках более серьезных приложений.
Надеюсь, что в каждом из примеров, представленных в этом руководстве, я смог показать как магические методы могут помочь в повседневных задачах. И я буду не первым кто согласиться, что когда вам обьясняют какой-либо материал, но не обьясняют его практическое применение это реально раздражает.
Я хочу перебрать массив с помощью foreach , чтобы проверить, существует ли значение. Если значение существует, я хочу удалить элемент, который его содержит.
У меня такой код:
Я не знаю, как удалить элемент после того, как значение найдено. Как мне его удалить?
Я должен использовать foreach для этой проблемы. Вероятно, существуют альтернативы foreach , и вы можете ими поделиться.
Если вы также получите ключ, вы можете удалить этот элемент следующим образом:
Лучшее решение - использовать функцию array_filter :
Как говорится в документации php:
Поскольку foreach полагается на указатель внутреннего массива в PHP 5, его изменение в цикле может привести к неожиданному поведению.
В PHP 7 foreach не использует указатель внутреннего массива.
Вместо того, чтобы выполнять цикл foreach () для массива, было бы быстрее использовать array_search () для поиска правильного ключа. На небольших массивах я бы использовал foreach для лучшей читаемости, но для больших массивов или часто выполняемого кода это должно быть немного более оптимальным:
Оператор строгого сравнения! == необходим, потому что array_search () может возвращать 0 в качестве индекса $ unwantedValue.
Кроме того, в приведенном выше примере будет удалено только первое значение $ unwantedValue, если $ unwantedValue может встречаться более одного раза в массиве $, вы должны использовать array_keys (), чтобы найти их все:
Если у вас есть сценарий, в котором вам нужно удалить более одного значения из массива foreach, в этом случае вам нужно передать значение по ссылке для каждого: я пытаюсь объяснить этот сценарий:
Во втором цикле вы хотите отключить записи первых циклов, которые не появляются снова в итерации для повышения производительности, или же затем сбрасываются из памяти, потому что в памяти они присутствуют и будут приходить в итерациях.
Уже есть ответы, которые проливают свет на то, как сбить с толку. Вместо того, чтобы повторять код во всех ваших классах, сделайте функцию, как показано ниже, и используйте ее в коде всякий раз, когда это необходимо. В бизнес-логике иногда не нужно раскрывать некоторые свойства. См. Ниже один лайнер-вызов для удаления
Как уже упоминалось, вы хотите выполнить foreach с ключом и отключить его с помощью ключа, но обратите внимание, что изменение массива во время итерации в целом является плохой идеей, хотя я не уверен в правилах PHP по этому поводу. навскидку.
Но учтите, что нельзя явно уничтожить объект .
Он останется там, однако, если вы отключите объект и ваш скрипт подтолкнет PHP к ограничениям памяти, ненужные объекты будут собраны мусором. Я бы выбрал unset() (вместо того, чтобы устанавливать для него значение null), поскольку он, кажется, имеет лучшую производительность (не проверено, но задокументировано на один из комментариев из официального руководства PHP).
Тем не менее, имейте в виду, что PHP всегда уничтожает объекты, как только страница обслуживается. Так что это нужно только для действительно длинных циклов и / или тяжелых страниц.
Я бы выбрал unset, потому что это может дать сборщику мусора более точную подсказку, чтобы память снова стала доступной раньше. Будьте осторожны, чтобы любые объекты, на которые указывает объект, либо имели другие ссылки, либо сначала сбрасывались, либо вам действительно придется ждать сборщика мусора, поскольку тогда для них не будет никаких дескрипторов.
Может быть в ситуации, когда вы создаете новый объект mysqli.
$MyConnection = new mysqli($hn, $un, $pw, $db);
Но даже после того, как вы закроете объект
В этом случае вы не можете использовать unlink() , потому что для unlink() потребуется строка имени пути, но в этом случае $MyConnection является объектом.
Итак, у вас есть другой выбор - установить для него значение null:
Теперь все идет хорошо, как вы и ожидали. У вас нет содержимого внутри переменной $MyConnection , а также вы уже очистили объект mysqli.
Рекомендуется закрыть объект перед установкой значения вашей переменной на null .
Это простое доказательство того, что вы не можете уничтожить объект, вы можете уничтожить только ссылку на него.
Краткий ответ: необходимы оба.
Я чувствую, что был дан правильный ответ, но минимально. Да, обычно unset () лучше всего подходит для "скорости", но если вы хотите немедленно освободить память (за счет процессора), следует использовать null.
Как уже упоминалось, установка значения null не означает, что все будет восстановлено, у вас могут быть объекты с общей памятью (неклонированные), которые предотвратят разрушение объекта. Более того, как говорили другие, вы все равно не можете «уничтожить» объекты явно, так что вам все равно не следует пытаться это сделать.
Вам нужно будет выяснить, что лучше для вас. Также вы можете использовать __destruct () для объекта, который будет вызываться при unset или null, но его следует использовать осторожно и, как говорили другие, никогда не вызывать напрямую!
Удобный пост, объясняющий несколько неправильных представлений об этом:
Это покрывает несколько заблуждений о том, как работает деструктор. Вызов его явно не приведет к уничтожению вашей переменной, согласно документу PHP5:
PHP 5 представляет концепцию деструктора, аналогичную концепции других объектно-ориентированных языков, таких как C ++. Метод деструктора будет вызываться, как только не будет других ссылок на конкретный объект, или в любом порядке во время завершения работы.
Заголовок может немного сбивать с толку, потому что я не совсем уверен, как описать свой «массив». Вот как это выглядит, когда я использую print_r .
Это вроде как объект с массивом объектов.
Я хочу удалить элемент (например, весь [1] ), но если я попытаюсь использовать unset, например unset($this->U16sArray[$arrayindex]) , я получаю .
Неустранимая ошибка: невозможно использовать объект типа stdClass как массив.
Я все еще очень смущен тем, как и почему мой «массив» вообще получается таким. Но я счастлив использовать его таким образом, если я могу удалить элемент.
Дополнительная информация по запросу. Он извлекается с помощью PDO из базы данных с помощью ..
Это часть гораздо большего объекта.
А затем декодируется из JSON с помощью
Я медленно ищу проблему. В принципе, все работает нормально, пока я не использую функцию unset, после чего что-то изменяется, сохраняется, а затем, когда я его перезагружаю, он начинает выдавать ошибки. Предположительно он изменился с одного типа массива на другой или что-то в этом роде.
Например, перед использованием функции unset для моих данных данные в моей базе данных выглядят так .
Когда я делаю print_r, он отображается как U16sArray -> Array.
После того, как неустановленное значение было использовано где-то в данных и снова сохранено, данные теперь выглядят следующим образом.
Итак, были добавлены «0» и «1». Теперь мой код неверен в разных местах, и print_r теперь показывает его как объект U16sArray -> stdClass.
РЕШЕНИЕ (я думаю) - Примерно 1/4 пути вниз по странице PHP: json_encode, я думаю, это ответ, на который ответил: «simoncpu was here». Очевидно, «Снятие установки элемента также приведет к удалению ключей. json_encode () теперь будет считать, что это объект, и закодирует его как таковой. '
Таким образом, может показаться, что он работает нормально, unset изменяет его на объект, а затем, когда я в следующий раз загружаю его, он больше не работает как массив.
Решение состоит в том, чтобы использовать array_values для повторного индексации массива перед кодированием / сохранением.
Читайте также: