Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 396 · После объектов и работы с изображениями

Imagick: атрибуты изображения, свойства и проверка метаданных

Артикул внутри PNG, переход с устаревших методов, различия PNG и TIFF и готовая карточка товара с отдельным описанием.

Передать артикул вместе с PNG и проверить файл

Вы выгружаете картинку товара для каталога и хотите передать вместе с ней артикул A-104 и описание. Эти сведения можно хранить как текстовые свойства изображения. Получатель прочитает их программой, а сам рисунок останется прежним. Для новой программы используем setImageProperty и getImageProperty; старые setImageAttribute/getImageAttribute разберём отдельно, чтобы миграция не закончилась ошибкой отсутствующего метода.

Один и тот же рисунок до и после записи свойств: пиксели одинаковые, сохранённый PNG дополнен артикулом и комментарием
Один и тот же рисунок до и после записи свойств: пиксели одинаковые, сохранённый PNG дополнен артикулом и комментарием

В catalog-first.png появятся comment = SKU A-104 | Blue storage box и catalog:sku = A-104. Надпись на коробке не возникает: метаданные — отдельные сведения в файле, а не нарисованный текст. Если нужна подпись под изображением на странице, её нужно вывести через HTML; такой законченный вариант есть в последнем примере.

$image = new Imagick(__DIR__ . "/source.png");
$image->setImageProperty("comment", "SKU A-104 | Blue storage box");
$image->writeImage(__DIR__ . "/catalog.png");
$read = new Imagick(__DIR__ . "/catalog.png");
echo $read->getImageProperty("comment");

Ожидаемый текст: SKU A-104 | Blue storage box. source.png — наш рисунок 400×320; в архиве он лежит в base64 и автоматически восстанавливается полным example.php. new Imagick(...) открывает файл и создаёт объект; $image сохраняет этот объект. Стрелка -> вызывает его метод. Две строки в скобках setter — имя поля и значение, запятая их разделяет. Точка с запятой завершает команду. __DIR__ даёт каталог скрипта, точка . соединяет строки пути. Второй объект $read открывает именно записанный файл: так мы проверяем результат сохранения, а не только память первого объекта.

comment — имя комментария, которое понимает используемый PNG-кодер. catalog:sku — выбранное нами имя поля для артикула. Оно не становится стандартом всех графических форматов. Setter возвращает true; getter возвращает значение. Эти сведения не подтверждают авторство или достоверность товара: их может записать любая программа. Нужны PHP с Imagick, PNG/TIFF-кодеры и право записи в каталог.

Подписать PNG артикулом и прочитать сохранённые сведения

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них.

Подписать PNG артикулом и прочитать сохранённые сведенияПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$setResult = $image->setImageProperty('comment', 'SKU A-104 | Blue storage box');
$image->setImageProperty('catalog:sku', 'A-104');
$comment = $image->getImageProperty('comment');
$saved = atSave($image, 'catalog-first.png');
$read = new Imagick(__DIR__ . '/catalog-first.png');
$readComment = $read->getImageProperty('comment');
$readSku = $read->getImageProperty('catalog:sku');
$images = ['before'=>atImage($source,'first-before8.png'),'after'=>atImage($read,'first-after8.png')];
$rects = atCompare([$source,$read],'attributes-first.png');
atDiagnostic('attributes-first',[
 'set_return'=>$setResult,'in_memory'=>atState($image),'saved'=>$saved,'readback'=>atState($read),'read_comment'=>$readComment,'read_sku'=>$readSku,
 'images'=>$images,'panel_keys'=>['before','after'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['setter_true'=>$setResult===true,'memory_value'=>$comment==='SKU A-104 | Blue storage box','stored_comment'=>$readComment===$comment,'stored_sku'=>$readSku==='A-104','pixels_unchanged'=>atHash($source)===atHash($image)&&atHash($source)===atHash($read),'same_geometry'=>$read->getImageGeometry()===['width'=>400,'height'=>320]]
]);
echo "attributes-first.png\nfirst-before8.png\nfirst-after8.png\ncatalog-first.png\nmetrics.json\ncomment=SKU A-104 | Blue storage box\nsku=A-104 pixels=unchanged setter=true\n";

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-first.png
first-before8.png
first-after8.png
catalog-first.png
metrics.json
comment=SKU A-104 | Blue storage box
sku=A-104 pixels=unchanged setter=true

Перейти со старых Attribute-методов и понять возвращаемое значение

Современный getter читает четыре состояния: комментарий, артикул, отсутствующий ключ false и существующую пустую строку
Современный getter читает четыре состояния: комментарий, артикул, отсутствующий ключ false и существующую пустую строку

Путь данных: строка "A-104" передаётся setImageProperty под именем "catalog:sku" → свойство хранится в текущем изображении → writeImage передаёт его кодеру формата → новый Imagick читает файл → getImageProperty возвращает найденную строку в место вызова. На этапе кодирования часть свойств может не попасть в выбранный формат или вернуться под другим именем. Поэтому проверка после записи — отдельный шаг.

Старый кодНовый кодОграничение
setImageAttribute(key, value)setImageProperty(key, value)Проверить результат сохранения нужного формата.
getImageAttribute(key)getImageProperty(key)Отличать false от пустой строки через ===.
Проверка номера версии без учёта сборкиmethod_exists(Imagick::class, имя)Наличие метода зависит также от ImageMagick и параметров сборки.
$value = $image->getImageProperty("missing-key");
if ($value === false) {
    echo "Такого поля нет";
}

Тройное равенство === проверяет и тип, и значение. false означает отсутствие свойства, а "" — существующую строку без символов. Обычная проверка if (!$value) смешала бы эти ситуации. Диаграмма показывает длину двух непустых ASCII-строк; крест обозначает false, пустая рамка — строку длиной ноль. Это модель прочитанных значений, а не устройство PNG-байтов. Текущая сборка не содержит ни одного Attribute-метода: программы проверяют доступность, но не имитируют их выполнение.

Заменить устаревшие Attribute-методы и различить false и пустую строку

В проверенной сборке оба Attribute-метода отсутствуют; их выполнения здесь нет. Современные Property-методы записали и прочитали значения. Отсутствующий missing-key вернул false, существующий empty-value — строку длиной ноль. Ошибки пустого изображения и неверного типа перехвачены.

Заменить устаревшие Attribute-методы и различить false и пустую строкуПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$availability = atAvailability();
$image->setImageProperty('comment', 'SKU A-104 | Blue storage box');
$image->setImageProperty('catalog:sku', 'A-104');
$image->setImageProperty('empty-value', '');
$missing = $image->getImageProperty('missing-key');
$empty = $image->getImageProperty('empty-value');
$saved = atSave($image, 'migrated.png');
$read = new Imagick(__DIR__ . '/migrated.png');
$properties = atProperties($read);
$diagram = atPresence($properties);
$images = ['card'=>atImage($read,'migration-card8.png'),'presence'=>atImage($diagram,'migration-presence8.png')];
$rects = atCompare([$read,$diagram],'attributes-migration.png');
$errors = ['empty_get'=>atTry(fn()=>(new Imagick())->getImageProperty('comment')),'empty_set'=>atTry(fn()=>(new Imagick())->setImageProperty('comment','x')),'array_name'=>atTry(fn()=>$image->setImageProperty([],'x'))];
atDiagnostic('attributes-migration',[
 'availability'=>$availability,'missing'=>['type'=>get_debug_type($missing),'value'=>$missing],'empty'=>['type'=>get_debug_type($empty),'value'=>$empty],'saved'=>$saved,'readback'=>atState($read),'errors'=>$errors,'diagram'=>['row_keys'=>['comment','catalog:sku','missing-key','empty-value'],'pixels_per_ASCII_byte'=>7,'missing_symbol'=>'red cross','empty_symbol'=>'green empty outline'],
 'images'=>$images,'panel_keys'=>['card','presence'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['legacy_unavailable'=>!$availability['getImageAttribute']&&!$availability['setImageAttribute'],'current_available'=>$availability['getImageProperty']&&$availability['setImageProperty'],'missing_false'=>$missing===false&&$properties['missing-key']['value']===false,'empty_string'=>$empty===''&&$properties['empty-value']['value']==='','pixels_unchanged'=>atHash($source)===atHash($read),'empty_get_caught'=>($errors['empty_get']['error']['class']??null)==='ImagickException','empty_set_caught'=>($errors['empty_set']['error']['class']??null)==='ImagickException','array_name_caught'=>($errors['array_name']['error']['class']??null)==='TypeError']
]);
echo "attributes-migration.png\nmigration-card8.png\nmigration-presence8.png\nmigrated.png\nmetrics.json\nlegacy=unavailable current=available missing=false empty=empty-string errors=3\n";

В проверенной сборке оба Attribute-метода отсутствуют; их выполнения здесь нет. Современные Property-методы записали и прочитали значения. Отсутствующий missing-key вернул false, существующий empty-value — строку длиной ноль. Ошибки пустого изображения и неверного типа перехвачены. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-migration.png
migration-card8.png
migration-presence8.png
migrated.png
metrics.json
legacy=unavailable current=available missing=false empty=empty-string errors=3

Проверить формат, очистку блоков и отличие свойства от EXIF

Два визуально одинаковых файла: PNG и TIFF после реальной записи, с результатами getter для тех же имён полей
Два визуально одинаковых файла: PNG и TIFF после реальной записи, с результатами getter для тех же имён полей

Одно изображение снабжено шестью строковыми полями и сохранено в PNG и TIFF. В памяти setter для каждого поля успешен. После PNG все шесть значений вернулись под исходными именами. После TIFF из нашего списка getImageProperty вернул только comment. Это наблюдение конкретной сборки и конкретных имён, а не утверждение, что TIFF умеет хранить только комментарии. Например, кодер записывает label в TIFFTAG_PAGENAME, но этот getter в проверенном чтении вернул false.

$image->setImageProperty("catalog:sku", "A-104");
$image->setImageFormat("tiff");
$image->writeImage(__DIR__ . "/catalog.tiff");
$read = new Imagick(__DIR__ . "/catalog.tiff");
var_dump($read->getImageProperty("catalog:sku")); // bool(false)

Для чтения чужого TIFF исследуйте его действительные теги и поддерживаемые имена; не делайте вывод о всех метаданных по одному false. В отдельном excluded.png настройка png:exclude-chunk=all запрещает запись необязательных блоков PNG. Все шесть проверяемых полей после чтения отсутствуют, хотя RGB совпадают с исходником. Это помогает объяснить, почему сохранение рисунка прошло успешно, а нужная подпись потерялась.

Проверить, что вернётся из PNG, TIFF и PNG без текстовых блоков

После записи PNG все шесть заданных полей прочитаны под теми же именами. После TIFF из проверяемых имён getter вернул только comment. В excluded.png все шесть getter вернули false. Поле Exif:Artist не создало EXIF-профиль. Во всех трёх файлах RGB остались прежними.

Проверить, что вернётся из PNG, TIFF и PNG без текстовых блоковПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$values = ['comment'=>'SKU A-104 | Blue storage box','label'=>'Catalog export','catalog:sku'=>'A-104','artist'=>'Example studio','Exif:Artist'=>'Not an EXIF profile','empty-value'=>''];
foreach ($values as $key => $value) {
    $image->setImageProperty($key, $value);
}
$saved = ['png'=>atSave($image,'formats.png'),'tiff'=>atSave($image,'formats.tiff','tiff'),'excluded'=>atSave($image,'excluded.png','png',true)];
$png = new Imagick(__DIR__.'/formats.png');
$tiff = new Imagick(__DIR__.'/formats.tiff');
$excluded = new Imagick(__DIR__.'/excluded.png');
$checks = ['pixels_unchanged'=>atHash($source)===atHash($png)&&atHash($source)===atHash($tiff)&&atHash($source)===atHash($excluded),'tiff_comment'=>$tiff->getImageProperty('comment')===$values['comment'],'png_property_does_not_create_EXIF_profile'=>count($png->getImageProfiles('exif',false))===0];
foreach ($values as $key => $value) {
    $checks['png_'.$key] = $png->getImageProperty($key) === $value;
    $checks['excluded_'.$key] = $excluded->getImageProperty($key) === false;
    if ($key !== 'comment') $checks['tiff_getter_'.$key] = $tiff->getImageProperty($key) === false;
}
$images = ['png'=>atImage($png,'formats-png8.png'),'tiff'=>atImage($tiff,'formats-tiff8.png')];
$rects = atCompare([$png,$tiff],'attributes-formats.png');
atDiagnostic('attributes-formats',[
 'assigned_values'=>$values,'in_memory'=>atState($image),'saved'=>$saved,'profiles'=>$png->getImageProfiles('*',false),'tiff_observation_scope'=>'getImageProperty readback for selected keys only; not a claim that no other TIFF tags exist',
 'images'=>$images,'panel_keys'=>['png','tiff'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,'checks'=>$checks
]);
echo "attributes-formats.png\nformats-png8.png\nformats-tiff8.png\nformats.png\nformats.tiff\nexcluded.png\nmetrics.json\npng-readback=6 tiff-selected-getters=comment excluded-readback=0 EXIF-profiles=0\n";

После записи PNG все шесть заданных полей прочитаны под теми же именами. После TIFF из проверяемых имён getter вернул только comment. В excluded.png все шесть getter вернули false. Поле Exif:Artist не создало EXIF-профиль. Во всех трёх файлах RGB остались прежними. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-formats.png
formats-png8.png
formats-tiff8.png
formats.png
formats.tiff
excluded.png
metrics.json
png-readback=6 tiff-selected-getters=comment excluded-readback=0 EXIF-profiles=0

Имя Exif:Artist в этой программе — строковое свойство. В PNG оно читается как текст, но настоящего EXIF-профиля не появилось: getImageProfiles("exif", false) вернул пустой список. Профиль — структурированные данные определённого формата; префикс в имени сам по себе его не создаёт. Для совместимого изменения EXIF/IPTC/XMP нужен инструмент и процедура, которые записывают именно этот формат метаданных. Простая замена имени Attribute на Property не решает такие задачи автоматически.

Собрать две версии и настоящую карточку каталога

Архивный PNG с двумя полями и публичный PNG после очистки: изображение одинаковое, подпись для страницы хранится отдельно
Архивный PNG с двумя полями и публичный PNG после очистки: изображение одинаковое, подпись для страницы хранится отдельно

Для собственного каталога удобно разделить две задачи. Архивная версия хранит встроенный комментарий и артикул. Публичная версия содержит изображение, а название и артикул берутся из отдельного public.json. Функция ниже принимает исходный Imagick, артикул и название; clone создаёт независимую копию, чтобы исходник не поменялся. Она записывает оба PNG, читает их обратно и возвращает массив отчёта.

$export = exportCatalogItem($source, "A-104", "Blue storage box");

Функция exportCatalogItem объявлена в полном примере ниже. return возвращает сведения в $export. Затем PHP читает public.json и создаёт public-card.html. Откройте этот HTML-файл: под картинкой будет A-104 — Blue storage box. Это настоящий видимый текст страницы, а не надежда, что браузер сам покажет встроенный comment. htmlspecialchars подготавливает текст для HTML, чтобы он отображался как текст, а не разметка.

Сохранить архивную и публичную версии с отдельной карточкой товара

archive.png содержит комментарий и артикул. public.png выглядит так же, но проверяемых встроенных полей в нём больше нет. public.json содержит данные карточки и SHA публичного файла. Откройте public-card.html: под реальным изображением появится подпись A-104 — Blue storage box.

Сохранить архивную и публичную версии с отдельной карточкой товараПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
function exportCatalogItem(Imagick $source, string $sku, string $title): array
{
    $archive = clone $source;
    $archive->setImageProperty('comment', 'SKU ' . $sku . ' | ' . $title);
    $archive->setImageProperty('catalog:sku', $sku);
    $archiveSaved = atSave($archive, 'archive.png');
    $archiveRead = new Imagick(__DIR__ . '/archive.png');

    $public = clone $archiveRead;
    $public->stripImage();
    $publicSaved = atSave($public, 'public.png');
    $publicRead = new Imagick(__DIR__ . '/public.png');
    $publicData = [
        'image' => 'public.png',
        'sku' => $archiveRead->getImageProperty('catalog:sku'),
        'title' => $title,
        'image_sha256' => hash_file('sha256', __DIR__ . '/public.png'),
    ];
    file_put_contents(__DIR__ . '/public.json', json_encode(
        $publicData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
    ) . "\n");
    return ['archive' => $archiveSaved, 'public' => $publicSaved,
        'public_data' => $publicData, 'public_readback' => atState($publicRead)];
}

$export = exportCatalogItem($source, 'A-104', 'Blue storage box');
$readData = json_decode((string) file_get_contents(__DIR__ . '/public.json'),
    true, 512, JSON_THROW_ON_ERROR);
$title = htmlspecialchars($readData['title'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$sku = htmlspecialchars($readData['sku'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$html = '<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' . $title . '</title><body><figure><img src="public.png" width="400" height="320" style="max-width:100%;height:auto" alt="' . $title . '"><figcaption>' . $sku . ' — ' . $title . '</figcaption></figure></body></html>';
file_put_contents(__DIR__ . '/public-card.html', $html);
$archiveRead = new Imagick(__DIR__.'/archive.png');
$publicRead = new Imagick(__DIR__.'/public.png');
$images = ['archive'=>atImage($archiveRead,'workflow-archive8.png'),'public'=>atImage($publicRead,'workflow-public8.png')];
$rects = atCompare([$archiveRead,$publicRead],'attributes-workflow.png');
atDiagnostic('attributes-workflow',[
 'export'=>$export,'read_data'=>$readData,'html'=>['filename'=>'public-card.html','sha256'=>hash_file('sha256',__DIR__.'/public-card.html'),'contains_caption'=>str_contains((string)file_get_contents(__DIR__.'/public-card.html'),'A-104 — Blue storage box')],
 'images'=>$images,'panel_keys'=>['archive','public'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['archive_comment'=>$archiveRead->getImageProperty('comment')==='SKU A-104 | Blue storage box','archive_sku'=>$archiveRead->getImageProperty('catalog:sku')==='A-104','public_no_comment'=>$publicRead->getImageProperty('comment')===false,'public_no_sku'=>$publicRead->getImageProperty('catalog:sku')===false,'JSON_readback'=>$readData===$export['public_data'],'sidecar_bound_to_file'=>$readData['image_sha256']===hash_file('sha256',__DIR__.'/public.png'),'pixels_unchanged'=>atHash($source)===atHash($archiveRead)&&atHash($source)===atHash($publicRead),'no_input_profiles'=>count($source->getImageProfiles('*',false))===0]
]);
echo "attributes-workflow.png\nworkflow-archive8.png\nworkflow-public8.png\narchive.png\npublic.png\npublic.json\npublic-card.html\nmetrics.json\narchive-sku=A-104 public-embedded-sku=false caption=A-104 | Blue storage box\n";

archive.png содержит комментарий и артикул. public.png выглядит так же, но проверяемых встроенных полей в нём больше нет. public.json содержит данные карточки и SHA публичного файла. Откройте public-card.html: под реальным изображением появится подпись A-104 — Blue storage box. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-workflow.png
workflow-archive8.png
workflow-public8.png
archive.png
public.png
public.json
public-card.html
metrics.json
archive-sku=A-104 public-embedded-sku=false caption=A-104 | Blue storage box

В этом собственном входе нет ICC-профиля. stripImage используется здесь как наглядная очистка проверяемых полей. На фотографиях она может убрать и полезные профили или другие сведения, поэтому не переносите операцию на весь архив без выбора требований. Результат этого примера доказывает отсутствие comment и catalog:sku в публичном PNG через getter; он не объявляется универсальным средством удаления любых скрытых данных. SHA в public.json связывает описание с конкретными байтами файла и обнаруживает случайную замену, но не является подписью автора.

В боевом каталоге основной артикул и описание разумно хранить также в базе данных или отдельном документе, поскольку последующая конвертация может потерять встроенные поля. JSON здесь — небольшой сопроводительный документ, а не начальный пример для изучения массивов. Запись свойств не требует рисования текста, однако полный экспорт всё равно читает, кодирует и записывает изображение: время и память зависят от размера файлов.

Полные исторические контракты, ошибки и упражнения

Imagick::getImageAttribute: исторический контракт и переход

Imagick::getImageAttribute(string $key): string

Метод устарел с Imagick 3.4.4; официальный мануал не рекомендует полагаться на него. Он сохранён здесь для понимания старого кода. Доступность в официальном справочнике: PECL imagick 2, PECL imagick 3. В проверенной сборке Imagick 3.8.1 с ImageMagick 7.1.1-46 метод отсутствует: method_exists вернул false. Исходники 3.8.1 включают его только при ImageMagick ниже 7 и без отключения устаревшего API. Это не утверждение, что он есть в любой сборке Imagick 3.

getImageAttribute получает обязательный строковый key — имя атрибута — и читает связанное значение. Официальная сигнатура указывает string, но раздел возвращаемого значения на странице пуст. В исторической ветке исходников 3.8.1 при отсутствии значения предусмотрен false; это вывод из кода, а не результат выполненного здесь вызова на ImageMagick 6. Современная замена — getImageProperty($key): она возвращает строку либо false, если свойство не найдено. В наших запусках false и пустая строка различены явно.

// Исторический код: не запускается в проверенной сборке IM7.
$value = $image->getImageAttribute("comment");

// Современная замена, реально проверенная ниже.
$value = $image->getImageProperty("comment");
if ($value === false) {
    echo "Комментарий отсутствует";
} else {
    echo $value;
}

Официальные страницы этих двух Attribute-методов не содержат отдельного описания исключений. Исторический setter в исходниках проверяет наличие изображения и переводит отказ библиотеки в ImagickException; исторический getter при отсутствующем значении возвращает false. Ни один из этих исторических сценариев не объявляется выполненным. Для текущих Property-методов наши программы реально получили ImagickException на пустом объекте и TypeError при массиве вместо строкового имени. Вызывайте методы после чтения либо создания изображения.

Связанные действия: свойства, профили и метаданные, пиксели и каналы и ошибки и исключения. Атрибут изображения здесь не связан с атрибутами PHP вида #[...].

Imagick::setImageAttribute: исторический контракт и переход

Imagick::setImageAttribute(string $key, string $value): bool

Метод устарел с Imagick 3.4.4; официальный мануал не рекомендует полагаться на него. Он сохранён здесь для понимания старого кода. Доступность в официальном справочнике: PECL imagick 2, PECL imagick 3. В проверенной сборке Imagick 3.8.1 с ImageMagick 7.1.1-46 метод отсутствует: method_exists вернул false. Исходники 3.8.1 включают его только при ImageMagick ниже 7 и без отключения устаревшего API. Это не утверждение, что он есть в любой сборке Imagick 3.

setImageAttribute получает два обязательных строковых аргумента: key — имя, value — значение. Официальный результат bool равен true при успехе. True подтверждает установку атрибута в объекте, а не сохранение произвольного поля в любом формате. Для нового кода используйте setImageProperty($key, $value), затем сохраните изображение и прочитайте нужные поля из нового объекта.

// Исторический код: не запускается в проверенной сборке IM7.
$image->setImageAttribute("comment", "SKU A-104");

// Современная замена, реально проверенная ниже.
$image->setImageProperty("comment", "SKU A-104");

Официальные страницы этих двух Attribute-методов не содержат отдельного описания исключений. Исторический setter в исходниках проверяет наличие изображения и переводит отказ библиотеки в ImagickException; исторический getter при отсутствующем значении возвращает false. Ни один из этих исторических сценариев не объявляется выполненным. Для текущих Property-методов наши программы реально получили ImagickException на пустом объекте и TypeError при массиве вместо строкового имени. Вызывайте методы после чтения либо создания изображения.

Связанные действия: свойства, профили и метаданные, пиксели и каналы и ошибки и исключения. Атрибут изображения здесь не связан с атрибутами PHP вида #[...].

Запуск, упражнение и продолжение

Каждый архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте всё рядом и выполните php example.php. Рисунок восстановится автоматически. Полные программы проверены в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16. Выполнены именно современные альтернативы; историческая Attribute-ветка на ImageMagick 6 здесь не запускалась.

Упражнение: setter вернул true, но после сохранения catalog:sku вернулся как false. Доказывает ли true, что поле было записано в файл? Ответ: нет, setter сообщил об установке в объекте. Проверьте поддержку свойства выбранным кодером и читайте новый объект из файла. Второе упражнение: поле равно "". Нужно ли заменять его подписью «не найдено»? Ответ: только если ваша политика считает пустую строку отсутствием; getter различает её и false. Третье упражнение: comment появился в PNG, но надписи на рисунке нет. Это правильно: для видимого текста нужны HTML-подпись или отдельная операция рисования.

Полный вспомогательный PHP из каждого архива
<?php
declare(strict_types=1);
function atRestore():void {$hashes=json_decode('{"source.png":"2ed876f4b498f2411738f3684cb8102b6a5f9c67162ea90cde540551a84f555d"}',true,512,JSON_THROW_ON_ERROR);foreach($hashes as $name=>$hash){$raw=base64_decode((string)file_get_contents(__DIR__.'/'.$name.'.b64'),true);if($raw===false||hash('sha256',$raw)!==$hash||file_put_contents(__DIR__.'/'.$name,$raw)===false)throw new RuntimeException('Input missing or changed');}}
function atHash(Imagick $i):string{return hash('sha256',json_encode($i->exportImagePixels(0,0,$i->getImageWidth(),$i->getImageHeight(),'RGB',Imagick::PIXEL_SHORT),JSON_THROW_ON_ERROR));}
function atProperties(Imagick $i):array {$out=[];foreach(['comment','label','catalog:sku','artist','Exif:Artist','missing-key','empty-value']as $key){$value=$i->getImageProperty($key);$out[$key]=['type'=>get_debug_type($value),'value'=>$value];}return $out;}
function atState(Imagick $i):array{return ['geometry'=>[$i->getImageWidth(),$i->getImageHeight()],'RGB16_sha256'=>atHash($i),'properties'=>atProperties($i),'profiles'=>$i->getImageProfiles('*',false)];}
function atSave(Imagick $i,string $name,string $format='png',bool $exclude=false):array {
    $c=clone $i;$c->setImageFormat($format);$c->setImageDepth(8);
    if($format==='png'){$c->setOption('png:bit-depth','8');$c->setOption('png:color-type','2');if($exclude)$c->setOption('png:exclude-chunk','all');}
    if($format==='tiff')$c->setImageCompression(Imagick::COMPRESSION_ZIP);
    if(!$c->writeImage(__DIR__.'/'.$name))throw new RuntimeException('Cannot save image');$r=new Imagick(__DIR__.'/'.$name);
    return ['filename'=>$name,'sha256'=>hash_file('sha256',__DIR__.'/'.$name),'bytes'=>filesize(__DIR__.'/'.$name),'state'=>atState($r)];
}
function atImage(Imagick $i,string $name):array {$saved=atSave($i,$name,'png',true);return ['geometry'=>[$i->getImageWidth(),$i->getImageHeight()],'RGB16_sha256'=>atHash($i),'encoded_png'=>['filename'=>$name,'RGB16_sha256'=>$saved['state']['RGB16_sha256']],'display_basis'=>'Actual PHP raster; metadata values are explained outside the image, not painted onto the product'];}
function atCompare(array $images,string $name):array {$i=new Imagick();$i->newImage(400*count($images),320,'white','png');$rects=[];foreach($images as $n=>$v){$i->compositeImage($v,Imagick::COMPOSITE_COPY,400*$n,0);$rects[]=['x'=>400*$n,'y'=>0,'width'=>400,'height'=>320];}atSave($i,$name,'png',true);return $rects;}
function atPresence(array $properties):Imagick {
    $i=new Imagick();$i->newImage(400,320,'#f7faf9','png');$d=new ImagickDraw();$d->setStrokeAntialias(false);
    foreach(['comment','catalog:sku','missing-key','empty-value']as $n=>$key){$value=$properties[$key]['value'];$y=30+$n*70;
        if($value===false){$d->setStrokeColor('#b03932');$d->setStrokeWidth(4);$d->line(20,$y,44,$y+24);$d->line(44,$y,20,$y+24);}
        elseif($value===''){$d->setFillColor('none');$d->setStrokeColor('#1a6970');$d->setStrokeWidth(3);$d->rectangle(20,$y,44,$y+24);}
        else{$d->setStrokeColor('none');$d->setFillColor('#1a6970');$d->rectangle(20,$y,20+min(350,strlen($value)*7),$y+24);}
    }$i->drawImage($d);return $i;
}
function atAvailability():array {$out=[];foreach(['getImageAttribute','setImageAttribute','getImageProperty','setImageProperty']as $name)$out[$name]=method_exists(Imagick::class,$name);return $out;}
function atTry(callable $f):array {try{$v=$f();return ['type'=>get_debug_type($v),'returned'=>$v];}catch(Throwable $e){return ['error'=>['class'=>get_class($e),'message'=>$e->getMessage()]];}}
function atDiagnostic(string $id,array $data):void {$doc=['schema'=>'aen-image-attributes-program-diagnostics-v1','example_id'=>$id,'runtime'=>['php'=>PHP_VERSION,'imagick'=>phpversion('imagick'),'imagemagick'=>Imagick::getVersion()],'legacy_methods_available'=>['getImageAttribute'=>method_exists(Imagick::class,'getImageAttribute'),'setImageAttribute'=>method_exists(Imagick::class,'setImageAttribute')],'legacy_methods_executed'=>false,'executed_api'=>['getImageProperty','setImageProperty']]+$data;if(file_put_contents(__DIR__.'/metrics.json',json_encode($doc,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR)."\n")===false)throw new RuntimeException('Cannot save metrics');}

atRestore восстанавливает собственный вход, atSave записывает и реально читает файл, atProperties получает значения выбранных полей, atPresence рисует схему их наличия. Изображения с суффиксом 8 в имени предназначены для иллюстраций и очищены от метаданных; проверяемые файлы перечислены отдельно в выводе программы. Исходные Property-вызовы видны в основном example.php.

Вопросы, из которых вырастают такие задачи

Обсуждения ниже задают практические ситуации. Утверждения о методах и форматах проверены по официальному мануалу, исходникам и собственным сохранённым файлам; тексты ответов и чужой код не копировались.

PHP Imagick: How to save custom image property to file: Свойство читается из объекта до записи, но исчезает при открытии сохранённого файла. Разделяем состояние объекта и реальный файл; записываем PNG/TIFF и читаем значения новыми объектами.

Imagick PHP: Setting geodata EXIF, but it's not being recognised?: Строки с префиксом exif: не распознаются внешним просмотрщиком как нужные EXIF-данные. Показываем отдельное текстовое Exif:Artist и пустой список EXIF-профилей; имя поля не выдаём за структуру профиля.

What type of metadata -strip removes?: Разработчик не понимает, какие метаданные теряет после strip. Проверяем конкретные поля до и после очистки, отделяя неизменные RGB от удалённых сведений и предупреждая об ICC в других входах.

ImageMagick convert -set comment not working: Добавленный comment кажется отсутствующим при просмотре PNG. Читаем реальный файл getter-методом и выводим комментарий отдельно; не обещаем, что всякий просмотрщик показывает любое поле.

Add metadata to PNG image using PHP: Требуется добавлять текстовые метки к группе PNG в каталоге. Даём короткое присваивание свойства и функцию экспорта с отдельной JSON-карточкой товара.

Which ImageMagick command can write image metadata?: Непонятно, можно ли записать метаданные и что увидит другая программа. Сравниваем записанные форматы и точные имена getter, затем строим настоящий HTML с подписью из сопроводительного документа.

Функции и методы этой темы

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

Imagick::getImageAttributeПрочитать старый атрибут: перейти на getImageProperty

PECL imagick 2, PECL imagick 3 · imagick

Imagick::getImageAttribute(string $key): string

Imagick::getImageAttribute: исторический контракт и переход

Imagick::getImageAttribute(string $key): string

Метод устарел с Imagick 3.4.4; официальный мануал не рекомендует полагаться на него. Он сохранён здесь для понимания старого кода. Доступность в официальном справочнике: PECL imagick 2, PECL imagick 3. В проверенной сборке Imagick 3.8.1 с ImageMagick 7.1.1-46 метод отсутствует: method_exists вернул false. Исходники 3.8.1 включают его только при ImageMagick ниже 7 и без отключения устаревшего API. Это не утверждение, что он есть в любой сборке Imagick 3.

getImageAttribute получает обязательный строковый key — имя атрибута — и читает связанное значение. Официальная сигнатура указывает string, но раздел возвращаемого значения на странице пуст. В исторической ветке исходников 3.8.1 при отсутствии значения предусмотрен false; это вывод из кода, а не результат выполненного здесь вызова на ImageMagick 6. Современная замена — getImageProperty($key): она возвращает строку либо false, если свойство не найдено. В наших запусках false и пустая строка различены явно.

// Исторический код: не запускается в проверенной сборке IM7.
$value = $image->getImageAttribute("comment");

// Современная замена, реально проверенная ниже.
$value = $image->getImageProperty("comment");
if ($value === false) {
    echo "Комментарий отсутствует";
} else {
    echo $value;
}

Официальные страницы этих двух Attribute-методов не содержат отдельного описания исключений. Исторический setter в исходниках проверяет наличие изображения и переводит отказ библиотеки в ImagickException; исторический getter при отсутствующем значении возвращает false. Ни один из этих исторических сценариев не объявляется выполненным. Для текущих Property-методов наши программы реально получили ImagickException на пустом объекте и TypeError при массиве вместо строкового имени. Вызывайте методы после чтения либо создания изображения.

Связанные действия: свойства, профили и метаданные, пиксели и каналы и ошибки и исключения. Атрибут изображения здесь не связан с атрибутами PHP вида #[...].

Заменить устаревшие Attribute-методы и различить false и пустую строку

В проверенной сборке оба Attribute-метода отсутствуют; их выполнения здесь нет. Современные Property-методы записали и прочитали значения. Отсутствующий missing-key вернул false, существующий empty-value — строку длиной ноль. Ошибки пустого изображения и неверного типа перехвачены.

Заменить устаревшие Attribute-методы и различить false и пустую строкуПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$availability = atAvailability();
$image->setImageProperty('comment', 'SKU A-104 | Blue storage box');
$image->setImageProperty('catalog:sku', 'A-104');
$image->setImageProperty('empty-value', '');
$missing = $image->getImageProperty('missing-key');
$empty = $image->getImageProperty('empty-value');
$saved = atSave($image, 'migrated.png');
$read = new Imagick(__DIR__ . '/migrated.png');
$properties = atProperties($read);
$diagram = atPresence($properties);
$images = ['card'=>atImage($read,'migration-card8.png'),'presence'=>atImage($diagram,'migration-presence8.png')];
$rects = atCompare([$read,$diagram],'attributes-migration.png');
$errors = ['empty_get'=>atTry(fn()=>(new Imagick())->getImageProperty('comment')),'empty_set'=>atTry(fn()=>(new Imagick())->setImageProperty('comment','x')),'array_name'=>atTry(fn()=>$image->setImageProperty([],'x'))];
atDiagnostic('attributes-migration',[
 'availability'=>$availability,'missing'=>['type'=>get_debug_type($missing),'value'=>$missing],'empty'=>['type'=>get_debug_type($empty),'value'=>$empty],'saved'=>$saved,'readback'=>atState($read),'errors'=>$errors,'diagram'=>['row_keys'=>['comment','catalog:sku','missing-key','empty-value'],'pixels_per_ASCII_byte'=>7,'missing_symbol'=>'red cross','empty_symbol'=>'green empty outline'],
 'images'=>$images,'panel_keys'=>['card','presence'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['legacy_unavailable'=>!$availability['getImageAttribute']&&!$availability['setImageAttribute'],'current_available'=>$availability['getImageProperty']&&$availability['setImageProperty'],'missing_false'=>$missing===false&&$properties['missing-key']['value']===false,'empty_string'=>$empty===''&&$properties['empty-value']['value']==='','pixels_unchanged'=>atHash($source)===atHash($read),'empty_get_caught'=>($errors['empty_get']['error']['class']??null)==='ImagickException','empty_set_caught'=>($errors['empty_set']['error']['class']??null)==='ImagickException','array_name_caught'=>($errors['array_name']['error']['class']??null)==='TypeError']
]);
echo "attributes-migration.png\nmigration-card8.png\nmigration-presence8.png\nmigrated.png\nmetrics.json\nlegacy=unavailable current=available missing=false empty=empty-string errors=3\n";

В проверенной сборке оба Attribute-метода отсутствуют; их выполнения здесь нет. Современные Property-методы записали и прочитали значения. Отсутствующий missing-key вернул false, существующий empty-value — строку длиной ноль. Ошибки пустого изображения и неверного типа перехвачены. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-migration.png
migration-card8.png
migration-presence8.png
migrated.png
metrics.json
legacy=unavailable current=available missing=false empty=empty-string errors=3

Подписать PNG артикулом и прочитать сохранённые сведения

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них.

Подписать PNG артикулом и прочитать сохранённые сведенияПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$setResult = $image->setImageProperty('comment', 'SKU A-104 | Blue storage box');
$image->setImageProperty('catalog:sku', 'A-104');
$comment = $image->getImageProperty('comment');
$saved = atSave($image, 'catalog-first.png');
$read = new Imagick(__DIR__ . '/catalog-first.png');
$readComment = $read->getImageProperty('comment');
$readSku = $read->getImageProperty('catalog:sku');
$images = ['before'=>atImage($source,'first-before8.png'),'after'=>atImage($read,'first-after8.png')];
$rects = atCompare([$source,$read],'attributes-first.png');
atDiagnostic('attributes-first',[
 'set_return'=>$setResult,'in_memory'=>atState($image),'saved'=>$saved,'readback'=>atState($read),'read_comment'=>$readComment,'read_sku'=>$readSku,
 'images'=>$images,'panel_keys'=>['before','after'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['setter_true'=>$setResult===true,'memory_value'=>$comment==='SKU A-104 | Blue storage box','stored_comment'=>$readComment===$comment,'stored_sku'=>$readSku==='A-104','pixels_unchanged'=>atHash($source)===atHash($image)&&atHash($source)===atHash($read),'same_geometry'=>$read->getImageGeometry()===['width'=>400,'height'=>320]]
]);
echo "attributes-first.png\nfirst-before8.png\nfirst-after8.png\ncatalog-first.png\nmetrics.json\ncomment=SKU A-104 | Blue storage box\nsku=A-104 pixels=unchanged setter=true\n";

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-first.png
first-before8.png
first-after8.png
catalog-first.png
metrics.json
comment=SKU A-104 | Blue storage box
sku=A-104 pixels=unchanged setter=true
Imagick::setImageAttributeЗаписать старый атрибут: перейти на setImageProperty

PECL imagick 2, PECL imagick 3 · imagick

Imagick::setImageAttribute(string $key, string $value): bool

Imagick::setImageAttribute: исторический контракт и переход

Imagick::setImageAttribute(string $key, string $value): bool

Метод устарел с Imagick 3.4.4; официальный мануал не рекомендует полагаться на него. Он сохранён здесь для понимания старого кода. Доступность в официальном справочнике: PECL imagick 2, PECL imagick 3. В проверенной сборке Imagick 3.8.1 с ImageMagick 7.1.1-46 метод отсутствует: method_exists вернул false. Исходники 3.8.1 включают его только при ImageMagick ниже 7 и без отключения устаревшего API. Это не утверждение, что он есть в любой сборке Imagick 3.

setImageAttribute получает два обязательных строковых аргумента: key — имя, value — значение. Официальный результат bool равен true при успехе. True подтверждает установку атрибута в объекте, а не сохранение произвольного поля в любом формате. Для нового кода используйте setImageProperty($key, $value), затем сохраните изображение и прочитайте нужные поля из нового объекта.

// Исторический код: не запускается в проверенной сборке IM7.
$image->setImageAttribute("comment", "SKU A-104");

// Современная замена, реально проверенная ниже.
$image->setImageProperty("comment", "SKU A-104");

Официальные страницы этих двух Attribute-методов не содержат отдельного описания исключений. Исторический setter в исходниках проверяет наличие изображения и переводит отказ библиотеки в ImagickException; исторический getter при отсутствующем значении возвращает false. Ни один из этих исторических сценариев не объявляется выполненным. Для текущих Property-методов наши программы реально получили ImagickException на пустом объекте и TypeError при массиве вместо строкового имени. Вызывайте методы после чтения либо создания изображения.

Связанные действия: свойства, профили и метаданные, пиксели и каналы и ошибки и исключения. Атрибут изображения здесь не связан с атрибутами PHP вида #[...].

Подписать PNG артикулом и прочитать сохранённые сведения

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них.

Подписать PNG артикулом и прочитать сохранённые сведенияПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
$image = clone $source;
$setResult = $image->setImageProperty('comment', 'SKU A-104 | Blue storage box');
$image->setImageProperty('catalog:sku', 'A-104');
$comment = $image->getImageProperty('comment');
$saved = atSave($image, 'catalog-first.png');
$read = new Imagick(__DIR__ . '/catalog-first.png');
$readComment = $read->getImageProperty('comment');
$readSku = $read->getImageProperty('catalog:sku');
$images = ['before'=>atImage($source,'first-before8.png'),'after'=>atImage($read,'first-after8.png')];
$rects = atCompare([$source,$read],'attributes-first.png');
atDiagnostic('attributes-first',[
 'set_return'=>$setResult,'in_memory'=>atState($image),'saved'=>$saved,'readback'=>atState($read),'read_comment'=>$readComment,'read_sku'=>$readSku,
 'images'=>$images,'panel_keys'=>['before','after'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['setter_true'=>$setResult===true,'memory_value'=>$comment==='SKU A-104 | Blue storage box','stored_comment'=>$readComment===$comment,'stored_sku'=>$readSku==='A-104','pixels_unchanged'=>atHash($source)===atHash($image)&&atHash($source)===atHash($read),'same_geometry'=>$read->getImageGeometry()===['width'=>400,'height'=>320]]
]);
echo "attributes-first.png\nfirst-before8.png\nfirst-after8.png\ncatalog-first.png\nmetrics.json\ncomment=SKU A-104 | Blue storage box\nsku=A-104 pixels=unchanged setter=true\n";

PNG получил комментарий SKU A-104 | Blue storage box и отдельное поле catalog:sku=A-104. Оба значения действительно прочитаны из сохранённого catalog-first.png. Рисунок не изменился: сведения записаны рядом с пикселями, а не нарисованы поверх них. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-first.png
first-before8.png
first-after8.png
catalog-first.png
metrics.json
comment=SKU A-104 | Blue storage box
sku=A-104 pixels=unchanged setter=true

Сохранить архивную и публичную версии с отдельной карточкой товара

archive.png содержит комментарий и артикул. public.png выглядит так же, но проверяемых встроенных полей в нём больше нет. public.json содержит данные карточки и SHA публичного файла. Откройте public-card.html: под реальным изображением появится подпись A-104 — Blue storage box.

Сохранить архивную и публичную версии с отдельной карточкой товараПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/metadata-fixture.php';
atRestore();
$source=new Imagick(__DIR__.'/source.png');
$sourceBefore=atState($source);
$sourceHash=hash_file('sha256',__DIR__.'/source.png');
function exportCatalogItem(Imagick $source, string $sku, string $title): array
{
    $archive = clone $source;
    $archive->setImageProperty('comment', 'SKU ' . $sku . ' | ' . $title);
    $archive->setImageProperty('catalog:sku', $sku);
    $archiveSaved = atSave($archive, 'archive.png');
    $archiveRead = new Imagick(__DIR__ . '/archive.png');

    $public = clone $archiveRead;
    $public->stripImage();
    $publicSaved = atSave($public, 'public.png');
    $publicRead = new Imagick(__DIR__ . '/public.png');
    $publicData = [
        'image' => 'public.png',
        'sku' => $archiveRead->getImageProperty('catalog:sku'),
        'title' => $title,
        'image_sha256' => hash_file('sha256', __DIR__ . '/public.png'),
    ];
    file_put_contents(__DIR__ . '/public.json', json_encode(
        $publicData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
    ) . "\n");
    return ['archive' => $archiveSaved, 'public' => $publicSaved,
        'public_data' => $publicData, 'public_readback' => atState($publicRead)];
}

$export = exportCatalogItem($source, 'A-104', 'Blue storage box');
$readData = json_decode((string) file_get_contents(__DIR__ . '/public.json'),
    true, 512, JSON_THROW_ON_ERROR);
$title = htmlspecialchars($readData['title'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$sku = htmlspecialchars($readData['sku'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$html = '<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' . $title . '</title><body><figure><img src="public.png" width="400" height="320" style="max-width:100%;height:auto" alt="' . $title . '"><figcaption>' . $sku . ' — ' . $title . '</figcaption></figure></body></html>';
file_put_contents(__DIR__ . '/public-card.html', $html);
$archiveRead = new Imagick(__DIR__.'/archive.png');
$publicRead = new Imagick(__DIR__.'/public.png');
$images = ['archive'=>atImage($archiveRead,'workflow-archive8.png'),'public'=>atImage($publicRead,'workflow-public8.png')];
$rects = atCompare([$archiveRead,$publicRead],'attributes-workflow.png');
atDiagnostic('attributes-workflow',[
 'export'=>$export,'read_data'=>$readData,'html'=>['filename'=>'public-card.html','sha256'=>hash_file('sha256',__DIR__.'/public-card.html'),'contains_caption'=>str_contains((string)file_get_contents(__DIR__.'/public-card.html'),'A-104 — Blue storage box')],
 'images'=>$images,'panel_keys'=>['archive','public'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>atState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>atState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/source.png')===$sourceHash,
 'checks'=>['archive_comment'=>$archiveRead->getImageProperty('comment')==='SKU A-104 | Blue storage box','archive_sku'=>$archiveRead->getImageProperty('catalog:sku')==='A-104','public_no_comment'=>$publicRead->getImageProperty('comment')===false,'public_no_sku'=>$publicRead->getImageProperty('catalog:sku')===false,'JSON_readback'=>$readData===$export['public_data'],'sidecar_bound_to_file'=>$readData['image_sha256']===hash_file('sha256',__DIR__.'/public.png'),'pixels_unchanged'=>atHash($source)===atHash($archiveRead)&&atHash($source)===atHash($publicRead),'no_input_profiles'=>count($source->getImageProfiles('*',false))===0]
]);
echo "attributes-workflow.png\nworkflow-archive8.png\nworkflow-public8.png\narchive.png\npublic.png\npublic.json\npublic-card.html\nmetrics.json\narchive-sku=A-104 public-embedded-sku=false caption=A-104 | Blue storage box\n";

archive.png содержит комментарий и артикул. public.png выглядит так же, но проверяемых встроенных полей в нём больше нет. public.json содержит данные карточки и SHA публичного файла. Откройте public-card.html: под реальным изображением появится подпись A-104 — Blue storage box. Архив содержит example.php, source.png.b64 и metadata-fixture.php. Распакуйте их рядом и выполните php example.php: собственный PNG восстановится автоматически. Helper читает значения, сохраняет файлы и рисует диагностическую схему; полный код приведён в конце главы. Реально выполняются современные getImageProperty/setImageProperty. Устаревшие Attribute-методы на этой сборке отсутствуют и не выполнялись. Проверено в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
attributes-workflow.png
workflow-archive8.png
workflow-public8.png
archive.png
public.png
public.json
public-card.html
metrics.json
archive-sku=A-104 public-embedded-sku=false caption=A-104 | Blue storage box
Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.