Есть несколько способов вывести дату в таком формате:
Способ 1: IntlDateFormatter (рекомендуется)
$date = new DateTimeImmutable('2025-01-31');
$formatter = new IntlDateFormatter(
'ru_RU',
IntlDateFormatter::LONG,
IntlDateFormatter::NONE,
null,
null,
'd MMMM Y г.'
);
echo $formatter->format($date); // "31 января 2025 г.
В предыдущей части мы рассмотрели, что такое ZVAL в PHP. В это части более подробно рассмотрим, копирование, ссылки и возможные оптимизации.
Все примеры приведены для php 8.3. Для отладки примеров будем использовать метод xdebug_debug_zval. Для этого должно быть установлено расширение xdebug.
$var = 1;
xdebug_debug_zval('var');
Почему мы используем xdebug_debug_zval() вместо debug_zval_dump()?
Начиная с PHP 8.0, функция debug_zval_dump() перестала отображать критически важную информацию:
Не показывает refcount (счетчик ссылок)
Не отображает is_ref (флаг ссылочности)
В то время как xdebug_debug_zval() продолжает предоставлять полную информацию о внутренней структуре ZVAL:
Примечание: Функция xdebug_debug_zval() всегда показывает значение refcount на 1 больше реального. Это особенность реализации Xdebug — при выводе информации он временно увеличивает счетчик ссылок. В следующих примерах я буду указывать корректные значения refcount (уменьшенные на 1), чтобы отражать реальное состояние переменных.
Это означает, что в памяти хранится только одна копия строки, а все переменные ссылаются на один zval.
7. Измерение потребления памяти
<?php
function test1(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = $data;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test2(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = $data;
$copy[0] = 1;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test3(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = &$data;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function test4(): void
{
$data = range(1, 100000);
$memBefore = memory_get_usage();
$copy = &$data;
$copy[0] = 1;
$memAfter = memory_get_usage();
showMemory($memBefore, $memAfter);
}
function showMemory($memBefore, $memAfter): void
{
echo "Память до копирования: {$memBefore} байт\n";
echo "Память после копирования: {$memAfter} байт\n";
echo "Разница: " . ($memAfter - $memBefore) . " байт\n";
}
// 1. Присвоение одного массива другому. Без изменения данных.
test1();
// Память не увеличилась.
// Память до копирования: 2514944 байт
// Память после копирования: 2514944 байт
// Разница: 0 байт
// 2. Присвоение одного массива другому. С изменением данных.
test2();
// В результате память увеличилась в двое, т.к. массив был скопирован.
// Память до копирования: 2514976 байт
// Память после копирования: 4628592 байт
// Разница: 2113616 байт
// 3. Присвовение массива по ссылке, без изменения данных
test3();
// Выделяется память только на ссылку.
// Память до копирования: 2514976 байт
// Память после копирования: 2515008 байт
// Разница: 32 байт
// 4. Присвовение массива по ссылке, с изменением данных
test4();
// Выделяется память только на ссылку.
// Память до копирования: 2514976 байт
// Память после копирования: 2515008 байт
// Разница: 32 байт
Директива declare(strict_types=1) — это мощный инструмент в PHP, который обеспечивает строгую проверку типов данных. В этой статье мы подробно разберём, как правильно использовать strict_types, какие преимущества это даёт и как избежать распространённых ошибок.
Что такое strict_types?
strict_types=1 — это директива, которая включает строгую проверку типов для скалярных значений (int, float, string, bool) в пределах файла, где она объявлена.
<?php
declare(strict_types=1);
function sum(int $a, int $b): int {
return $a + $b;
}
sum("1", "2"); // Вызывает TypeError
Основные преимущества
Предотвращение скрытых ошибок типизации
Улучшение читаемости кода
Более предсказуемое поведение
Лучшая поддержка IDE
Практические примеры
Пример с Doctrine Entity
declare(strict_types=1);
#[ORM\Entity]
class Product {
#[ORM\Column(type: 'integer')]
private int $id;
public function setId(int $id): void {
$this->id = $id; // Ошибка при передаче строки
}
}
Работа с API
declare(strict_types=1);
$data = json_decode($response, true);
processOrder((int)$data['id']); // Явное приведение типа
Важные особенности
Действует только в файле, где объявлен
Не влияет на производительность
Всегда проверяет объекты и массивы
Разрешает null для nullable-типов
Рекомендации по внедрению
Начинать с новых файлов
Постепенно добавлять в существующий код
Использовать вместе с phpstan/psalm
Избегать глобального включения через php.ini
Заключение
Strict Types — это важный инструмент для создания надёжного и поддерживаемого кода на PHP. Его использование особенно важно в современных проектах и при работе с такими фреймворками, как Symfony и Laravel.
При работе с Doctrine ORM в Symfony часто возникает необходимость получить только идентификатор сущности, не загружая все её данные из базы данных. Это особенно важно для оптимизации производительности при работе с большими объемами данных.
Почему это важно?
Уменьшение количества запросов к БД
Снижение потребления памяти
Повышение скорости выполнения операций
Оптимизация работы с ассоциациями
Способы получения ID без загрузки сущности
1. Использование getReference()
// Получаем ссылку на сущность без её загрузки
$entityReference = $entityManager->getReference(Product::class, $id);
$entityId = $entityReference->getId();
2. DQL с выборкой только ID
$query = $entityManager->createQuery(
'SELECT p.id FROM App\Entity\Product p WHERE p.price > :price'
)->setParameter('price', 100);
$ids = $query->getResult(); // Возвращает массив ID
// Получаем ID связанной сущности без её полной загрузки
$categoryId = $product->getCategory()->getId();
// Doctrine использует прокси-объекты для ленивой загрузки
Практический пример
Рассмотрим реальный сценарий — нам нужно получить список ID товаров, которые нужно обновить:
public function getProductIdsToUpdate(EntityManagerInterface $em): array
{
return $em->getRepository(Product::class)
->createQueryBuilder('p')
->select('p.id')
->where('p.updatedAt < :date')
->setParameter('date', new \DateTime('-1 week'))
->getQuery()
->getSingleColumnResult();
}
Заключение
Использование этих методов позволяет значительно оптимизировать работу с базой данных в Symfony-приложениях. Выбор конкретного способа зависит от вашего сценария использования, но в большинстве случаев QueryBuilder с явным указанием select(‘id’) будет наиболее оптимальным решением.
Помните, что преждевременная оптимизация может быть вредна — используйте эти подходы там, где действительно есть проблемы с производительностью.
Быстрая сортировка — один из самых эффективных алгоритмов сортировки с средней сложностью O(n log n). В этой статье разберём принцип работы и реализации на PHP и JavaScript.
📌 Принцип работы Quick Sort
Выбор опорного элемента (pivot)
Разделение массива на две части: элементы меньше pivot и больше pivot
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
// Пример использования
const array = [10, 80, 30, 90, 40, 50, 70];
console.log(quickSort(array));
2. Оптимизированная версия (in-place)
function quickSortInPlace(arr, left = 0, right = arr.length - 1) {
if (left >= right) return;
const pivot = partition(arr, left, right);
quickSortInPlace(arr, left, pivot - 1);
quickSortInPlace(arr, pivot + 1, right);
return arr;
}
function partition(arr, left, right) {
const pivot = arr[right];
let i = left;
for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
}
}
[arr[i], arr[right]] = [arr[right], arr[i]];
return i;
}
// Пример использования
const nums = [10, 80, 30, 90, 40, 50, 70];
console.log(quickSortInPlace([...nums]));
⚡ Сравнение Quick Sort с другими алгоритмами
Характеристика
Quick Sort
Merge Sort
Bubble Sort
Средняя сложность
O(n log n)
O(n log n)
O(n²)
Худший случай
O(n²)
O(n log n)
O(n²)
Память
O(log n)
O(n)
O(1)
Стабильность
Нет
Да
Да
📌 Когда использовать Quick Sort?
Преимущества:
Обычно быстрее других алгоритмов на практике
Требует мало дополнительной памяти
Хорошо работает с кэшем процессора
Недостатки:
Худший случай O(n²) (редко при правильном выборе pivot)
Нестабильный
Оптимальное применение: сортировка больших массивов в памяти, когда стабильность не важна.
🔍 Дополнение: Нестабильность Quick Sort на практике
Критически важное отличие Quick Sort от стабильных алгоритмов вроде Merge Sort — его нестабильность. Это означает, что при наличии одинаковых элементов их относительный порядок после сортировки может измениться.
Наглядный пример
// Данные: пользователи с одинаковым возрастом
const users = [
{ name: 'Анна', age: 25 },
{ name: 'Иван', age: 30 },
{ name: 'Мария', age: 25 } // Такое же значение age, как у Анны
];
// После Quick Sort возможен вариант:
[
{ name: 'Мария', age: 25 }, // Мария теперь перед Анной!
{ name: 'Анна', age: 25 },
{ name: 'Иван', age: 30 }
]
Когда это критично?
При сортировке таблиц по нескольким столбцам (например: сначала по дате, потом по имени)
В финансовых системах, где важен порядок операций с одинаковой суммой
При работе с хронологическими данными (новости, события)
Как решить проблему?
Добавить индекс сравнения:
arr.map((item, index) => ({ ...item, _index: index })) // Сортировка с учётом _index при равенстве
Использовать стабильные аналоги:
Merge Sort
Встроенный Array.prototype.sort() (стабилен в современных браузерах)
Timsort (Python, Java)
Ситуация
Рекомендация
Важна скорость + нет одинаковых ключей
Quick Sort (оптимальный выбор)
Работа с объектами/дублями
Merge Sort или встроенная сортировка
Ограниченная память
In-place Quick Sort (но без стабильности)
Это дополнение помогает понять, почему в некоторых случаях разработчики предпочитают Merge Sort, несмотря на его повышенные требования к памяти.