İçeriğe geç

Form alanı değerlerini doğrulayın ve etkileşimli durumu koruyarak düzleştirin

Üretim ortamındaki bir form hattı, ham girdiyi genellikle doğrudan düzleştirmez. Önce her değeri doğrularsınız. Sonra hangi alanların kalıcı hale geleceğine, hangilerinin düzenlenebilir kalacağına karar verirsiniz. NextPDF core, bunun için iki yapı taşı sağlar: alanı oluştururken değerini belgeye yazan HasFormFields oluşturma trait’i ve her alanı statik sayfa grafiklerine işleyerek etkileşimli formu kaldıran flattenForms().

Bu tarif, core’un özellikle uygulama katmanına bıraktığı doğrulama adımını bu iki yapı taşıyla birleştirir. Şunları yapacaksınız:

  • Herhangi bir alan oluşturmadan önce bir değer eşlemesini alan başına kurallara göre doğrulayın; böylece geçersiz bir değer belgeye asla ulaşmaz.
  • Tek bir doğrulanmış veri kümesi oluşturun, ardından bunu iki kez işleyin — bir kez düzleştirilmiş olarak (kilitli, salt okunur bir kopya), bir kez de etkileşimli olarak (düzenlenebilir bir kopya) — böylece aynı alan durumu her iki çıktıya da taşınır.

Ön koşullar: çalışan bir NextPDF core kurulumu (composer require nextpdf/core) ve bu tarifin bir araya getirdiği alan oluşturma ile düzleştirme mekanizmalarını anlatan PDF formu oluşturma ve ön doldurma ve Form alanlarını düzleştirme konularını okumuş olmanız.

Kapsam sınırı. Core’un flattenForms() işlemi belgenin tamamına uygulanır: ya tüm alanları düzleştirir ya da hiçbirini. Core form API’sinde herkese açık, alan başına çalışan bir düzleştirme anahtarı veya yerleşik değer doğrulayıcı yoktur. Bu nedenle “bazılarını düzleştir, diğerlerini düzenlenebilir tut” işlemi uygulama katmanında gerçekleşir: bir kez doğrulayın, ardından aynı doğrulanmış veri kümesini iki belgeye işleyin. Bu tarif söz konusu deseni belgeler; alan başına çalışan bir core yöntemi varsaymaz.

Terminal window
composer require nextpdf/core

Ek bir uzantıya ihtiyacınız yoktur. Form oluşturma trait’i de düzleştirici de core ile birlikte gelir.

Bir Acrobat form (AcroForm) alanı, geçerli değerini alan sözlüğünün V girdisinde saklar. flattenForms() her alanın V değerini okur ve bunu alanın ait olduğu sayfanın içerik akışına işler — metin alanları BT ... Tj ... ET metnine, onay kutuları ve radyo düğmeleri çizilmiş yollara dönüşür, seçim alanları ise seçili öğelerini işler. Ardından /AcroForm katalog girdisini kaldırır. Sonuç, etkileşimli olmayan bir formdur: alanların, herhangi bir okuyucuda aynı şekilde görüntülenen ve form doldurma yeteneği gerektirmeyen statik bir gösterimi (ISO 32000-2 12.7).

Üretim desenini iki gerçek belirler:

  1. Core, alan değerlerini doğrulamaz. Her oluşturma yöntemi (textField(), comboBox(), checkBox() ve diğerleri), ilettiğiniz değeri doğrudan V içine yazar. E-posta biçimi, izin verilen seçeneklere üyelik ve zorunlu alanların mevcudiyeti uygulamanın sorumluluğundadır. Oluşturmadan önce doğrulayın ve içine geçersiz bir değer gömülmüş bir belge yaymak yerine bir kural ihlalinde hızlıca başarısız olun.

  2. Düzleştirme geri alınamaz ve belge geneline uygulanır. flattenForms() ve save() çağrısından sonra alanlar statik grafiklere dönüşür. Düzenlenebilir bir kopya tutmak için düzleştirmeyi geri almaya çalışmazsınız — doğrulanmış veri kümesini flattenForms() çağırmadan ikinci kez işlersiniz. Her iki kopya da aynı doğrulanmış değerlerden başlar; böylece kilitli kopya ve düzenlenebilir kopya aynı alan durumunu taşır.

Yeniden üretilebilirlik profili structuraldır: her belge, iki çalıştırma karşılaştırılmadan önce son geçişte normalleştirilen bir treyler /ID dizisi taşır.

NextPDF\Core\Document (NextPDF\Core\Concerns\HasFormFields aracılığıyla):

  • textField(string $name, float $x, float $y, float $w, float $h, string $default = '', array $options = []): static — değeri default ile verilen bir metin alanı oluşturur.
  • comboBox(string $name, float $x, float $y, float $w, float $h, array $items, string $selected = ''): static — seçili öğesi selected ile verilen bir açılır liste oluşturur.
  • checkBox(string $name, float $x, float $y, float $size, bool $checked = false): static — durumu checked ile verilen bir onay kutusu oluşturur.
  • flattenForms(): static — her alanın değerini statik sayfa içeriğine işler ve AcroForm’u kaldırır. Hiç alan yoksa işlemsizdir. Dahili olarak NextPDF\Form\FormFlattener sınıfına devreder.

NextPDF\Core\Concerns\HasOutput:

  • save(string $path): void — PDF’yi oluşturur ve yazar. Çıktı yolu bir akış sarmalayıcı olduğunda, bir boş bayt içerdiğinde veya var olmayan bir üst dizine işaret ettiğinde NextPDF\Exception\InvalidConfigException fırlatır.

Aşağıdaki örneklerdeki doğrulayıcı, bir core sembolü değil, size ait uygulama kodudur. Core’un bir değer doğrulama API’si yoktur; doğrulama adımının burada açıkça yer almasının nedeni budur.

Bu minimal akış, bir değer eşlemesini doğrular, bu eşlemeden üç alan oluşturur, ardından kilitli bir kopyayı düzleştirip kaydeder.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
/** @var array<string, string> $input Untrusted value map (e.g. from a request). */
$input = [
'full_name' => 'Ada Lovelace',
'email' => '[email protected]',
'country' => 'Taiwan',
];
$allowedCountries = ['United Kingdom', 'Taiwan', 'Japan'];
// Validate before authoring: core writes whatever you pass, so reject early.
if (trim($input['full_name']) === '') {
throw new InvalidArgumentException('full_name must not be empty.');
}
if (filter_var($input['email'], FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException('email is not a valid address.');
}
if (!in_array($input['country'], $allowedCountries, true)) {
throw new InvalidArgumentException('country is not an allowed option.');
}
$doc = Document::createStandalone();
$doc->setTitle('Validated Form (locked copy)');
$doc->addPage();
$doc->textField(name: 'full_name', x: 20, y: 30, w: 90, h: 8, default: $input['full_name']);
$doc->textField(name: 'email', x: 20, y: 45, w: 90, h: 8, default: $input['email']);
$doc->comboBox(
name: 'country',
x: 20, y: 60, w: 90, h: 8,
items: $allowedCountries,
selected: $input['country'],
);
// Whole-document flatten: every field becomes static graphics.
$doc->flattenForms();
$doc->save(__DIR__ . '/registration-locked.pdf');
echo "Wrote registration-locked.pdf\n";

Üretim akışı, doğrulamayı işleme adımından ayırır. Türlenmiş bir FieldRuleSet, değer eşlemesini bir kez doğrular ve doğrulanmış bir veri kümesi döndürür. Tek bir renderForm() yardımcısı alanları oluşturur ve iki kez çalışır — kilitli kopya için düzleştirmeyle, düzenlenebilir kopya için düzleştirmesiz. Her iki kopya da aynı doğrulanmış değerlerden gelir; böylece etkileşimli durum onlara taşınır. Üst seviye kod, çıktı yollarını NEXTPDF_COOKBOOK_LOCKED_OUTPUT ve NEXTPDF_COOKBOOK_EDITABLE_OUTPUT değişkenlerinden okur.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\InvalidConfigException;
/**
* A single validated form record. Immutable: once constructed, every value has
* already passed the rule set, so the rendering step cannot re-introduce a bad
* value. This is the "validate once, render many" boundary.
*/
final readonly class ValidatedRegistration
{
/**
* @param non-empty-string $fullName
* @param non-empty-string $email
* @param non-empty-string $country
*/
public function __construct(
public string $fullName,
public string $email,
public string $country,
public bool $newsletter,
) {}
}
/**
* Application-layer field validation. Core performs no value validation, so the
* rules live here. Each method throws on the first violation; the caller maps
* the exception to an HTTP 422 or a user-facing message.
*/
final class FieldRuleSet
{
/** @var list<non-empty-string> */
private const array ALLOWED_COUNTRIES = ['United Kingdom', 'Taiwan', 'Japan', 'Germany'];
/**
* @param array<string, string|bool> $input Untrusted value map.
*
* @throws InvalidArgumentException When any field fails its rule.
*/
public function validate(array $input): ValidatedRegistration
{
$fullName = $this->requireNonEmpty($input, 'full_name');
$email = $this->requireEmail($input, 'email');
$country = $this->requireAllowed($input, 'country', self::ALLOWED_COUNTRIES);
$newsletter = $input['newsletter'] ?? false;
if (!is_bool($newsletter)) {
throw new InvalidArgumentException('newsletter must be a boolean.');
}
return new ValidatedRegistration(
fullName: $fullName,
email: $email,
country: $country,
newsletter: $newsletter,
);
}
/**
* @param array<string, string|bool> $input
*
* @return non-empty-string
*
* @throws InvalidArgumentException
*/
private function requireNonEmpty(array $input, string $key): string
{
$value = $input[$key] ?? '';
if (!is_string($value) || trim($value) === '') {
throw new InvalidArgumentException(sprintf('%s must not be empty.', $key));
}
return $value;
}
/**
* @param array<string, string|bool> $input
*
* @return non-empty-string
*
* @throws InvalidArgumentException
*/
private function requireEmail(array $input, string $key): string
{
$value = $input[$key] ?? '';
if (!is_string($value) || $value === '' || filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException(sprintf('%s is not a valid email address.', $key));
}
return $value;
}
/**
* @param array<string, string|bool> $input
* @param list<non-empty-string> $allowed
*
* @return non-empty-string
*
* @throws InvalidArgumentException
*/
private function requireAllowed(array $input, string $key, array $allowed): string
{
$value = $input[$key] ?? '';
if (!is_string($value) || !in_array($value, $allowed, true)) {
throw new InvalidArgumentException(sprintf('%s is not an allowed option.', $key));
}
return $value;
}
/** @return list<non-empty-string> */
public function allowedCountries(): array
{
return self::ALLOWED_COUNTRIES;
}
}
/**
* Author the same field layout from one validated record. When $flatten is
* true, every field is baked into static page content and the AcroForm is
* dropped; when false, the fields stay interactive and editable. Both paths
* start from identical values, preserving the form's state across copies.
*/
function renderForm(ValidatedRegistration $record, FieldRuleSet $rules, bool $flatten): Document
{
$doc = Document::createStandalone();
$doc->setTitle($flatten ? 'Customer Registration (locked)' : 'Customer Registration (editable)');
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Customer Registration', newLine: true);
$doc->ln(4);
$leftMargin = 15.0;
$fieldX = 70.0;
$fieldW = 120.0;
$fieldH = 8.0;
$rowSpacing = 12.0;
$y = 40.0;
$doc->setFont('helvetica', '', 10);
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Full Name:');
$doc->textField(name: 'full_name', x: $fieldX, y: $y, w: $fieldW, h: $fieldH, default: $record->fullName);
$y += $rowSpacing;
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Email:');
$doc->textField(name: 'email', x: $fieldX, y: $y, w: $fieldW, h: $fieldH, default: $record->email);
$y += $rowSpacing;
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Country:');
$doc->comboBox(
name: 'country',
x: $fieldX,
y: $y,
w: $fieldW,
h: $fieldH,
items: $rules->allowedCountries(),
selected: $record->country,
);
$y += $rowSpacing;
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Newsletter:');
$doc->checkBox(name: 'newsletter', x: $fieldX, y: $y, size: 5, checked: $record->newsletter);
if ($flatten) {
// Whole-document flatten: locked, read-only copy.
$doc->flattenForms();
}
return $doc;
}
/** @var array<string, string|bool> $input Untrusted value map (request payload). */
$input = [
'full_name' => 'Ada Lovelace',
'email' => '[email protected]',
'country' => 'Taiwan',
'newsletter' => true,
];
$rules = new FieldRuleSet();
try {
// Validate once. A violation aborts before any document is built.
$record = $rules->validate($input);
// Render the validated dataset twice: locked, then editable.
$locked = renderForm($record, $rules, flatten: true);
$editable = renderForm($record, $rules, flatten: false);
$lockedPath = getenv('NEXTPDF_COOKBOOK_LOCKED_OUTPUT')
?: __DIR__ . '/registration-locked.pdf';
$editablePath = getenv('NEXTPDF_COOKBOOK_EDITABLE_OUTPUT')
?: __DIR__ . '/registration-editable.pdf';
$locked->save($lockedPath);
$editable->save($editablePath);
echo "Wrote locked and editable registration copies\n";
} catch (InvalidArgumentException $e) {
// Validation failure: a bad value never reached the document.
fwrite(STDERR, 'Form validation failed: ' . $e->getMessage() . "\n");
exit(1);
} catch (InvalidConfigException $e) {
// Output failure: bad path, missing directory, or stream wrapper.
fwrite(STDERR, sprintf(
'PDF save failed for key "%s": %s' . "\n",
$e->getConfigKey(),
$e->getMessage(),
));
exit(1);
}

Beklenen çıktı:

Wrote locked and editable registration copies

Kilitli kopya, etkileşimli alanlar olmadan açılır — değerler statik grafiklere dönüşmüştür. Düzenlenebilir kopya, aynı değerler ön doldurulmuş halde açılır ve her alan düzenlenebilir kalır. Her ikisi de tek bir doğrulanmış veri kümesini yansıtır.

  • Oluşturmadan önce doğrulayın, sonrasında değil. Oluşturma yöntemleri, ilettiğiniz değeri V içine olduğu gibi yazar. Core’da hiçbir kanca, hatalı biçimli bir değeri save() sırasında reddetmez; bu nedenle doğrulamayı atlamış bir değer, kurtarma yolu olmadan düzleştirilmiş kopyaya gömülür.
  • flattenForms() ya hep ya hiçtir. Belgedeki tüm alanları düzleştirir. Bazı alanları düzenlenebilir tutmak için, üretim örneğindeki gibi düzleştirme çağrısı olmadan ikinci bir belge işleyin — core API’sinde alan başına bir anahtar beklemeyin.
  • Çağrı sırası. Önce her alanı oluşturun, ardından flattenForms(), ardından save() çağırın. Hiç alan yokken flattenForms() çağırmak güvenli biçimde hiçbir işlem yapmaz; bu yöntemi save() sonrasında çağırmak, zaten yazılmış baytları etkilemez.
  • Alan adları benzersiz olmalıdır. Aynı adı paylaşan iki alan, uyumlu okuyucularda paylaşılan tek bir değere sahip tek bir mantıksal alana dönüşür. Adlar veriden türetiliyorsa benzersiz olduklarını doğrulayın.
  • Düzleştirmede onay kutusunun doğru kabul edilen değeri. Düzleştirilmiş bir onay kutusu, değeri Yes, On, 1 veya true olduğunda onay işaretini çizer; boş veya Off bir değer yalnızca kutuyu çizer. İşlenen durumun doğrulanmış değerinizle eşleşmesi için checkBox() yöntemine gerçek bir boole değeri iletin.
  • İmza alanları asla düzleştirilmez. Bir /Sig alanının yüzeyi, yeniden işlenebilir bir değer değil, imza yükünden üretilen görünümdür; bu nedenle düzleştirici onu atlar. İmzalamadan önce düzleştirin, sonrasında asla.
  • Düzenlenebilir kopya hâlâ düzenlenebilirdir. Durumu düzenlenebilir kopyada korumak, alıcının onu değiştirebileceği anlamına gelir. Kilitli kopyayı yetkili kayıt, düzenlenebilir kopyayı ise çalışma taslağı olarak değerlendirin.

Doğrulama, alan sayısıyla doğrusaldır ve her veri kümesi için bir kez çalışır. Her işleme geçişi, alan başına bir widget açıklaması ve bir görünüm oluşturur. Düzleştirme adımı, alan başına sınırlı bir içerik bloğu ekler. Veri kümesini iki kez işlemek, belge başına maliyeti kabaca ikiye katlar; bu da birkaç yüz alanlı formlar için 1500 ms / 64 MB bütçesinin oldukça içinde kalır. Yalnızca tek bir çıktıya ihtiyacınız varsa, bir kez işleyin ve ikinci geçişi atlayın.

  • Güvenilmeyen girdiyi sınırda doğrulayın. E-posta biçimi, izin verilen seçeneklere üyelik ve zorunlu alanların mevcudiyeti, core bunları zorlamadığı için uygulama kodunda uygulanır. Güvenilmeyen girdiden türetilen herhangi bir değeri, bir alana ulaşmadan önce kaçışlayın veya normalleştirin; çünkü core onu belgeye olduğu gibi yazar.
  • Düzleştirme bir erişim denetimi değildir. Düzleştirilmiş bir değer normal bir okuyucuda düzenlenemez, ancak sayfa içeriğinde görünür kalır ve herhangi bir metin aracıyla çıkarılabilir. Düzleştirmeyi bir redaksiyon ya da hassas değerlerin korunması olarak değerlendirmeyin.
  • Düzenlenebilir kopya aynı verileri taşır. Onu yalnızca bu değerleri görmeye ve değiştirmeye yetkili taraflara dağıtın. İçerik hassas olduğunda, her iki kopyayı da İzinlerle şifreleme ile birleştirin ve orada açıklanan okuyucu iş birliğine bağlı uyarıyı dikkate alın: izin bitleri okuma kısıtlamalarını zorlamaz.
  • Kapalı başarısız olun. Üretim örneği, kısmi veya geçersiz bir belge yazmak yerine bir doğrulama veya çıktı hatasında sıfırdan farklı bir kodla çıkar. Bu istisnaları asla yutmayın.
İfadeSpesifikasyonMaddereference_id
Düzleştirilmiş bir form, alanların etkileşimli olmayan (statik) bir gösterimidir.ISO 32000-212.7

NextPDF, atıfta bulunulan maddede açıklanan statik yapıyı üretir; genel ISO 32000-2 uygunluğu iddia etmez. Bu tarifteki doğrulama kuralları, standardın bir uygunluk gereksinimi değil, uygulama politikasıdır.