Skip to content

Contracts / Barcode

The barcode domain defines four contracts: a one-dimensional (1D) encoder, a two-dimensional (2D) encoder, a generic registry-discoverable encoder, and a GS1 data parser. Together, they define the shape barcode services must satisfy. Symbology implementations register against these contracts.

Terminal window
composer require nextpdf/core:^3

A barcode encoder turns a payload string into the module matrix that the Portable Document Format (PDF) writer paints. NextPDF splits encoder contracts by dimensionality. Barcode1DEncoderInterface handles linear symbologies, including Code 128 and EAN-13, and returns a BarcodeData value object. Barcode2DEncoderInterface handles matrix symbologies, including Quick Response (QR) Code and Data Matrix. It returns a Barcode2DData value object with an options map for symbology-specific settings such as the error-correction level.

BarcodeEncoderInterface is the generic service-provider contract. Any 2D encoder discoverable through BarcodeEncoderRegistry implements it. The contract returns either a monochrome Barcode2DData or a color BarcodeColorData matrix, so a registered encoder can produce a colored symbol without a separate interface. Encoders are expected to be stateless beyond construction-time configuration. The registry returns one shared instance per registered type, so any per-call state would be a defect.

Gs1DataParserInterface is the structured-data contract. It parses a GS1 element string into a typed object, then re-encodes that object for a QR Code, a Data Matrix, or a Code 128 carrier. This keeps the GS1 grammar separate from the symbology. The parser validates Application Identifiers once. The carrier-specific methods format the same parsed structure for each target. The four contracts are stable. BarcodeEncoderInterface is stable since 3.0.0; the others since 1.0.0. New methods arrive only with default implementations.

TypeKindKey membersStabilitySince
Barcode1DEncoderInterfaceinterfaceencode(string): BarcodeDatastable1.0.0
Barcode2DEncoderInterfaceinterfaceencode(string, array): Barcode2DDatastable1.0.0
BarcodeEncoderInterfaceinterfaceencode(string, array): Barcode2DData|BarcodeColorDatastable3.0.0
Gs1DataParserInterfaceinterfaceparse(), encodeForQrCode(), encodeForDataMatrix(), encodeForCode128()stable1.0.0

On the 2D contracts, the $options array is symbology-specific, such as an error-correction level for QR Code. The contract does not constrain its keys. Each registered encoder documents its own option set.

examples/10-barcodes.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Barcode\Barcode2DType;
use NextPDF\Barcode\BarcodeType;
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Barcode Examples');
$doc->addPage();
$doc->setFont('helvetica', '', 10);
$doc->cell(0, 6, 'Code 128:', newLine: true);
$doc->write1DBarcode('NEXTPDF-2026', BarcodeType::C128, x: 15, y: null, w: 80, h: 20);
$doc->ln(28);
$doc->cell(0, 6, 'QR Code (URL):', newLine: true);
$doc->write2DBarcode('https://nextpdf.dev', Barcode2DType::QRCode, x: 15, y: null, w: 40, h: 40);
$doc->save(__DIR__ . '/output/10-barcodes.pdf');

write1DBarcode() and write2DBarcode() resolve an encoder through the registry. Your application code rarely touches the contracts directly. You name a symbology, and the registry supplies the encoder.

examples/contracts/barcode-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\Barcode2DEncoderInterface;
use NextPDF\Contracts\Gs1DataParserInterface;
use NextPDF\Exception\BarcodeException;
use Psr\Log\LoggerInterface;
final readonly class Gs1LabelService
{
public function __construct(
private Gs1DataParserInterface $parser,
private Barcode2DEncoderInterface $dataMatrix,
private LoggerInterface $logger,
) {}
/**
* Parse a GS1 element string and encode it as a Data Matrix.
*
* @param string $elementString A GS1 element string with Application Identifiers.
*/
public function encodeLabel(string $elementString): \NextPDF\Barcode\Barcode2DData
{
try {
$parsed = $this->parser->parse($elementString);
$payload = $this->parser->encodeForDataMatrix($parsed);
return $this->dataMatrix->encode($payload, ['errorCorrection' => 'high']);
} catch (BarcodeException $e) {
$this->logger->error('GS1 label encoding failed', [
'error' => $e->getMessage(),
]);
throw $e;
}
}
}

The service depends on the parser and encoder contracts. The catch block logs and rethrows the specific BarcodeException; it never catches a bare \Exception.

  • A registered encoder is shared. Per-call state on an encoder corrupts concurrent renders. Keep encoders stateless beyond constructor configuration.
  • BarcodeEncoderInterface::encode() may return color or monochrome data. Code that consumes it must handle both Barcode2DData and BarcodeColorData, not assume monochrome.
  • The 2D $options array is not validated by the contract. Most encoders silently ignore unknown keys. Verify the key name against the encoder’s own documentation.
  • GS1 parsing is grammar-strict. An element string with an unknown Application Identifier raises a BarcodeException rather than producing a partial parse. Validate upstream input.
  • 1D and 2D contracts are not interchangeable. Passing a QR payload to a 1D encoder produces an invalid symbol. The registry routes by symbology type, so prefer the registry over a direct contract call.

Encoding cost scales with payload length and target matrix size, not with the contract you call. A short Code 128 payload encodes in microseconds. A dense QR Code with high error correction is the heaviest 2D case. It is still well inside the performance_budget of 1500 ms wall and 64 MB peak for the multi-symbol example page. The matrix is computed once and painted as PDF operators. Reproducibility is bitwise because the same payload and options always yield the same module matrix. Registry lookup is O(1). The symbology algorithm does the work.

Barcode payloads are frequently attacker-influenced, such as a scanned Uniform Resource Locator (URL), a serial number, or a tracking code. The contracts encode bytes. They do not interpret them. A QR Code that encodes a hostile URL is still a valid QR Code, so payload trust is the consumer’s responsibility, not the encoder’s. Constrain payload length before encoding to bound matrix size and avoid a denial-of-service through an oversized symbol. The GS1 parser rejects malformed Application Identifiers, which removes one injection surface. It does not validate the semantic content of valid fields. Treat decoded barcode data as untrusted input wherever it re-enters the application.

ClaimStandardReference
QR Code symbols follow the QR Code matrix symbology specification.ISO/IEC 18004QR Code symbology
Data Matrix symbols follow the Data Matrix symbology specification.ISO/IEC 16022Data Matrix symbology
Code 128 symbols follow the Code 128 linear symbology specification.ISO/IEC 15417Code 128 symbology
GS1 element strings are parsed per the GS1 Application Identifier grammar.GS1 General SpecificationsApplication Identifiers

These standards are referenced by number and clause. They are not present in the verifiable citation corpus, so no reference_id is recorded. The engine paraphrases the requirement and cites the source. Consult the published standards for authoritative encoding rules.

Core defines and freezes the encoder contracts, and it ships the common symbologies. The Pro and Enterprise editions register an extended encoder set for additional barcode symbologies against the same BarcodeEncoderInterface, so a commercial deployment gains coverage without an application programming interface (API) change. Core resolves registered encoders through BarcodeEncoderRegistry. The contract surface is identical across editions.