From a706885f219fd026b3eff086913af90387ff3495 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Thu, 28 Aug 2025 14:13:37 -0300
Subject: [PATCH 1/2] feat: implement psalm
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.github/workflows/psalm.yml | 43 ++++++++++++++++++++++++++++++++++
composer.json | 2 ++
psalm.xml | 25 ++++++++++++++++++++
tests/psalm-baseline.xml | 2 ++
vendor-bin/psalm/composer.json | 10 ++++++++
5 files changed, 82 insertions(+)
create mode 100644 .github/workflows/psalm.yml
create mode 100644 psalm.xml
create mode 100644 tests/psalm-baseline.xml
create mode 100644 vendor-bin/psalm/composer.json
diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml
new file mode 100644
index 0000000..2765291
--- /dev/null
+++ b/.github/workflows/psalm.yml
@@ -0,0 +1,43 @@
+name: Static analysis
+
+on: pull_request
+
+concurrency:
+ group: psalm-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ static-analysis:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php:
+ - "8.1"
+ - "8.2"
+ - "8.3"
+ - "8.4"
+
+ name: static-psalm-analysis
+ steps:
+ - name: Checkout
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ persist-credentials: false
+
+ - name: Set up php${{ matrix.php }}
+ uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ ini-file: development
+ # Temporary workaround for missing pcntl_* in PHP 8.3
+ ini-values: disable_functions=
+
+ - name: Install dependencies
+ run: composer install
+
+ - name: Run coding standards check
+ run: vendor/bin/psalm --no-cache --threads=$(nproc) --output-format=github
diff --git a/composer.json b/composer.json
index 9e8b5e9..4ed80bb 100644
--- a/composer.json
+++ b/composer.json
@@ -37,6 +37,8 @@
"@composer bin all install --ansi",
"composer dump-autoload"
],
+ "psalm": "psalm --no-cache --threads=$(nproc)",
+ "psalm:update-baseline": "psalm --threads=$(nproc) --update-baseline --set-baseline=tests/psalm-baseline.xml",
"post-update-cmd": [
"composer dump-autoload"
],
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..a57485f
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
new file mode 100644
index 0000000..d04c5aa
--- /dev/null
+++ b/tests/psalm-baseline.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/vendor-bin/psalm/composer.json b/vendor-bin/psalm/composer.json
new file mode 100644
index 0000000..0dc7359
--- /dev/null
+++ b/vendor-bin/psalm/composer.json
@@ -0,0 +1,10 @@
+{
+ "require-dev": {
+ "vimeo/psalm": "^5.26"
+ },
+ "config": {
+ "platform": {
+ "php": "8.1"
+ }
+ }
+}
From 9a65b50a268e12db4f45ccdd63fbf871ca850fa2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Thu, 28 Aug 2025 14:22:57 -0300
Subject: [PATCH 2/2] fix: apply fixes reported by psalm
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
src/Command.php | 77 +++++++++++++-----------
src/DataFields.php | 30 +++++----
src/FdfFile.php | 9 ++-
src/InfoFields.php | 29 ++++-----
src/InfoFile.php | 27 ++++++---
src/Pdf.php | 147 +++++++++++++++++++++++++--------------------
src/XfdfFile.php | 46 ++++++++------
7 files changed, 203 insertions(+), 162 deletions(-)
diff --git a/src/Command.php b/src/Command.php
index aa9f886..16d9192 100644
--- a/src/Command.php
+++ b/src/Command.php
@@ -3,6 +3,8 @@
namespace mikehaertl\pdftk;
use mikehaertl\shellcommand\Command as BaseCommand;
+use mikehaertl\tmp\File;
+use Override;
/**
* Command
@@ -16,38 +18,35 @@
class Command extends BaseCommand
{
/**
- * @var string the pdftk binary
+ * The pdftk binary
*/
protected $_command = 'pdftk';
/**
- * @var array list of input files to process as array('name' => $filename,
+ * List of input files to process as array('name' => $filename,
* 'password' => $pw) indexed by handle
*/
- protected $_files = array();
+ protected array $_files = array();
/**
- * @var array list of command options, either strings or array with
- * arguments to addArg()
+ * List of command options, either strings or array with arguments to addArg()
*/
- protected $_options = array();
+ protected array $_options = array();
/**
- * @var string the operation to perform
+ * The operation to perform
*/
- protected $_operation;
+ protected ?string $_operation = null;
/**
- * @var string|array operation arguments, e.g. a list of page ranges or a
- * filename or tmp file instance
+ * Operation arguments, e.g. a list of page ranges or a filename or tmp file instance
*/
- protected $_operationArgument = array();
+ protected string|array|File $_operationArgument = array();
/**
- * @var bool whether to force escaping of the operation argument e.g. for
- * filenames
+ * Whether to force escaping of the operation argument e.g. for filenames
*/
- protected $_escapeOperationArgument = false;
+ protected bool $_escapeOperationArgument = false;
/**
* @param string $name the PDF file to add for processing
@@ -57,7 +56,7 @@ class Command extends BaseCommand
* @return Command the command instance for method chaining
* @throws \Exception
*/
- public function addFile($name, $handle, $password = null)
+ public function addFile($name, $handle, $password = null): self
{
$this->checkExecutionStatus();
$file = array(
@@ -76,7 +75,7 @@ public function addFile($name, $handle, $password = null)
* use Command default setting.
* @return Command the command instance for method chaining
*/
- public function addOption($option, $argument = null, $escape = null)
+ public function addOption($option, $argument = null, ?bool $escape = null): self
{
$this->_options[] = $argument === null ? $option : array($option, $argument, $escape);
return $this;
@@ -86,7 +85,7 @@ public function addOption($option, $argument = null, $escape = null)
* @param string $operation the operation to perform
* @return Command the command instance for method chaining
*/
- public function setOperation($operation)
+ public function setOperation($operation): self
{
$this->checkExecutionStatus();
$this->_operation = $operation;
@@ -102,11 +101,11 @@ public function getOperation()
}
/**
- * @param string $value the operation argument
+ * @param string|array $value the operation argument
* @param bool $escape whether to escape the operation argument
* @return Command the command instance for method chaining
*/
- public function setOperationArgument($value, $escape = false)
+ public function setOperationArgument(string|array|File $value, bool $escape = false): self
{
$this->checkExecutionStatus();
$this->_operationArgument = $value;
@@ -118,7 +117,7 @@ public function setOperationArgument($value, $escape = false)
* @return string|array|null the current operation argument as string or
* array or null if none set
*/
- public function getOperationArgument()
+ public function getOperationArgument(): string|array|null
{
// Typecast to string in case we have a File instance as argument
return is_array($this->_operationArgument) ? $this->_operationArgument : (string) $this->_operationArgument;
@@ -127,7 +126,7 @@ public function getOperationArgument()
/**
* @return int the number of files added to the command
*/
- public function getFileCount()
+ public function getFileCount(): int
{
return count($this->_files);
}
@@ -144,17 +143,20 @@ public function getFileCount()
* only a single file was added.
* @param string|null $qualifier the page number qualifier, either 'even'
* or 'odd' or null for none
- * @param string $rotation the rotation to apply to the pages.
+ * @param string|null $rotation the rotation to apply to the pages.
* @return Command the command instance for method chaining
*/
- public function addPageRange($start, $end = null, $handle = null, $qualifier = null, $rotation = null)
- {
+ public function addPageRange(
+ int|string|array $start,
+ int|string|null $end = null,
+ string|null $handle = null,
+ string|null $qualifier = null,
+ string|null $rotation = null,
+ ) {
$this->checkExecutionStatus();
if (is_array($start)) {
if ($handle !== null) {
- $start = array_map(function ($p) use ($handle) {
- return $handle . $p;
- }, $start);
+ $start = array_map(fn ($p) => $handle . $p, $start);
}
$range = implode(' ', $start);
} else {
@@ -164,6 +166,13 @@ public function addPageRange($start, $end = null, $handle = null, $qualifier = n
}
$range .= $qualifier . $rotation;
}
+ if (!is_array($this->_operationArgument)) {
+ if (!empty($this->_operationArgument)) {
+ $this->_operationArgument = array($this->_operationArgument);
+ } else {
+ $this->_operationArgument = array();
+ }
+ }
$this->_operationArgument[] = $range;
return $this;
}
@@ -173,6 +182,7 @@ public function addPageRange($start, $end = null, $handle = null, $qualifier = n
* null if none
* @return bool whether the command was executed successfully
*/
+ #[Override]
public function execute($filename = null)
{
$this->checkExecutionStatus();
@@ -185,7 +195,7 @@ public function execute($filename = null)
/**
* Process input PDF files and create respective command arguments
*/
- protected function processInputFiles()
+ protected function processInputFiles(): void
{
$passwords = array();
foreach ($this->_files as $handle => $file) {
@@ -204,10 +214,11 @@ protected function processInputFiles()
/**
* Process options and create respective command arguments
+ *
* @param string|null $filename if provided an 'output' option will be
* added
*/
- protected function processOptions($filename = null)
+ protected function processOptions($filename = null): void
{
// output must be first option after operation
if ($filename !== null) {
@@ -225,13 +236,10 @@ protected function processOptions($filename = null)
/**
* Process opearation and create respective command arguments
*/
- protected function processOperation()
+ protected function processOperation(): void
{
if ($this->_operation !== null) {
$value = $this->_operationArgument ? $this->_operationArgument : null;
- if ($value instanceof TmpFile) {
- $value = (string) $value;
- }
$this->addArg($this->_operation, $value, $this->_escapeOperationArgument);
}
}
@@ -239,9 +247,10 @@ protected function processOperation()
/**
* Ensure that the command was not exectued yet. Throws exception
* otherwise.
+ *
* @throws \Exception
*/
- protected function checkExecutionStatus()
+ protected function checkExecutionStatus(): void
{
if ($this->getExecuted()) {
throw new \Exception('Operation was already executed');
diff --git a/src/DataFields.php b/src/DataFields.php
index a5b40f7..5a97e06 100644
--- a/src/DataFields.php
+++ b/src/DataFields.php
@@ -11,11 +11,12 @@
* @author Ray Holland
* @author Michael Härtl
* @license http://www.opensource.org/licenses/MIT
+ * @extends ArrayObject
*/
class DataFields extends ArrayObject
{
- private $_string;
- private $_array;
+ private string $_string;
+ private array $_array;
/**
* DataFields constructor.
@@ -24,26 +25,23 @@ class DataFields extends ArrayObject
* @param int $flags
* @param string $iterator_class
*/
- public function __construct($input = null, $flags = 0, $iterator_class = "ArrayIterator")
- {
+ public function __construct(
+ ?string $input = null,
+ int $flags = 0,
+ string $iterator_class = "ArrayIterator",
+ ) {
$this->_string = $input ?: '';
$this->_array = self::parse($this->_string);
- return parent::__construct($this->_array, $flags, $iterator_class);
+ parent::__construct($this->_array, $flags, $iterator_class);
}
- /**
- * @return string
- */
- public function __toString()
+ public function __toString(): string
{
return $this->_string;
}
- /**
- * @return array
- */
- public function __toArray()
+ public function __toArray(): array
{
return $this->_array;
}
@@ -78,7 +76,7 @@ public function __toArray()
* @param $input the string to parse
* @return array the parsed result
*/
- public static function parse($input)
+ public static function parse(string $input): array
{
if (strncmp('---', $input, 3) === 0) {
// Split blocks only if '---' is followed by 'FieldType'
@@ -105,7 +103,7 @@ public static function parse($input)
* @param string $block the block to parse
* @return array the parsed block values indexed by respective names
*/
- public static function parseBlock($block)
+ public static function parseBlock(string $block): array
{
$data = array();
$lines = preg_split("/(\r\n|\n|\r)/", trim($block));
@@ -156,7 +154,7 @@ public static function parseBlock($block)
* 'FieldValueDefault' can span multiple lines
* @return bool whether the value continues in line n + 1
*/
- protected static function lineContinues($lines, $n, $key)
+ protected static function lineContinues(array $lines, int $n, string $key): bool
{
return
in_array($key, array('FieldValue', 'FieldValueDefault')) &&
diff --git a/src/FdfFile.php b/src/FdfFile.php
index 3081bf3..7184614 100644
--- a/src/FdfFile.php
+++ b/src/FdfFile.php
@@ -41,8 +41,13 @@ class FdfFile extends File
* created. Autodetected if not provided.
* @param string|null $encoding of the data. Default is 'UTF-8'.
*/
- public function __construct($data, $suffix = null, $prefix = null, $directory = null, $encoding = 'UTF-8')
- {
+ public function __construct(
+ array $data,
+ ?string $suffix = null,
+ ?string $prefix = null,
+ ?string $directory = null,
+ ?string $encoding = 'UTF-8',
+ ) {
if ($directory === null) {
$directory = self::getTempDir();
}
diff --git a/src/InfoFields.php b/src/InfoFields.php
index 9599e14..b152b6b 100644
--- a/src/InfoFields.php
+++ b/src/InfoFields.php
@@ -10,12 +10,13 @@
*
* @author Burak USGURLU
* @license http://www.opensource.org/licenses/MIT
+ * @extends ArrayObject
*/
class InfoFields extends ArrayObject
{
- private $_string;
+ private string $_string;
- private $_array;
+ private array $_array;
/**
* InfoFields constructor.
@@ -24,26 +25,23 @@ class InfoFields extends ArrayObject
* @param int $flags
* @param string $iterator_class
*/
- public function __construct($input = null, $flags = 0, $iterator_class = "ArrayIterator")
- {
+ public function __construct(
+ ?string $input = null,
+ int $flags = 0,
+ string $iterator_class = "ArrayIterator",
+ ) {
$this->_string = $input ?: '';
$this->_array = $this->parseData($this->_string);
- return parent::__construct($this->_array, $flags, $iterator_class);
+ parent::__construct($this->_array, $flags, $iterator_class);
}
- /**
- * @return string
- */
- public function __toString()
+ public function __toString(): string
{
return $this->_string;
}
- /**
- * @return array
- */
- public function __toArray()
+ public function __toArray(): array
{
return $this->_array;
}
@@ -70,11 +68,8 @@ public function __toArray()
* BookmarkTitle: Second bookmark
* BookmarkLevel: 1
* BookmarkPageNumber: 2
- *
- * @param $dataString
- * @return array
*/
- private function parseData($dataString)
+ private function parseData(string $dataString): array
{
$output = array();
foreach (explode(PHP_EOL, $dataString) as $line) {
diff --git a/src/InfoFile.php b/src/InfoFile.php
index 7315a6f..61321fd 100644
--- a/src/InfoFile.php
+++ b/src/InfoFile.php
@@ -3,6 +3,7 @@
namespace mikehaertl\pdftk;
use Exception;
+use mikehaertl\pdftk\InfoFields;
use mikehaertl\tmp\File;
/**
@@ -70,7 +71,7 @@ class InfoFile extends File
* or 'PdfID0' are ignored as those are not part of the PDF's metadata.
* All array elements are optional.
* @param string|null $suffix the optional suffix for the tmp file
- * @param string|null $suffix the optional prefix for the tmp file. If null
+ * @param string|null $prefix the optional prefix for the tmp file. If null
* 'php_tmpfile_' is used.
* @param string|null $directory directory where the file should be
* created. Autodetected if not provided.
@@ -80,8 +81,13 @@ class InfoFile extends File
* @throws Exception on invalid data format or if mbstring extension is
* missing and data must be converted
*/
- public function __construct($data, $suffix = null, $prefix = null, $directory = null, $encoding = 'UTF-8')
- {
+ public function __construct(
+ array|InfoFields $data,
+ ?string $suffix = null,
+ ?string $prefix = null,
+ ?string $directory = null,
+ ?string $encoding = 'UTF-8',
+ ) {
if ($suffix === null) {
$suffix = '.txt';
}
@@ -102,7 +108,7 @@ public function __construct($data, $suffix = null, $prefix = null, $directory =
}
$fields = '';
- $normalizedData = self::normalize($data);
+ $normalizedData = self::normalize((array) $data);
foreach ($normalizedData as $block => $items) {
$fields .= self::renderBlock($block, $items, $encoding);
@@ -123,7 +129,7 @@ public function __construct($data, $suffix = null, $prefix = null, $directory =
* @param array $data the data to normalize
* @return array a normalized array in the format described in the constructor
*/
- private static function normalize($data)
+ private static function normalize(array $data): array
{
$normalized = array();
foreach ($data as $key => $value) {
@@ -147,7 +153,7 @@ private static function normalize($data)
* @param string $encoding the encoding of the item data
* @return string the rendered fields
*/
- private static function renderBlock($block, $items, $encoding)
+ private static function renderBlock(string $block, array $items, string $encoding): string
{
$fields = '';
foreach ($items as $key => $value) {
@@ -173,8 +179,13 @@ private static function renderBlock($block, $items, $encoding)
* @param bool $isInfo whether it's an 'Info' field
* @return string the rendered field
*/
- private static function renderField($prefix, $key, $value, $encoding, $isInfo)
- {
+ private static function renderField(
+ string $prefix,
+ string $key,
+ string $value,
+ string $encoding,
+ bool $isInfo,
+ ): string {
if ($encoding !== 'UTF-8') {
$value = mb_convert_encoding($value, 'UTF-8', $encoding);
$key = mb_convert_encoding($key, 'UTF-8', $encoding);
diff --git a/src/Pdf.php b/src/Pdf.php
index f7c02f0..937a0b2 100644
--- a/src/Pdf.php
+++ b/src/Pdf.php
@@ -22,65 +22,65 @@ class Pdf
private const TMP_PREFIX = 'tmp_php_pdftk_';
/**
- * @var bool whether to ignore any errors if some non-empty output file was
+ * Whether to ignore any errors if some non-empty output file was
* still created. Default is false.
*/
- public $ignoreWarnings = false;
+ public bool $ignoreWarnings = false;
/**
- * @var null|string an optional directory where temporary files should be
+ * An optional directory where temporary files should be
* created. If left empty the directory is autodetected.
*/
- public $tempDir;
+ public ?string $tempDir = null;
/**
- * @var File the temporary output file
+ * The temporary output file
*/
- protected $_tmpFile;
+ protected ?File $_tmpFile = null;
/**
- * @var string the content type of the tmp output
+ * The content type of the tmp output
*/
- protected $_tmpOutputContentType = 'application/pdf';
+ protected string $_tmpOutputContentType = 'application/pdf';
/**
- * @var Command the command instance that executes pdftk
+ * The command instance that executes pdftk
*/
- protected $_command;
+ protected ?Command $_command = null;
/**
- * @var int a counter for autogenerated handles
+ * A counter for autogenerated handles
*/
- protected $_handle = 0;
+ protected int $_handle = 0;
/**
- * @var string the error message
+ * The error message
*/
- protected $_error = '';
+ protected string $_error = '';
/**
- * @var string|null the output filename. If null (default) a tmp file is
+ * The output filename. If null (default) a tmp file is
* used as output. If false, no output option is added at all.
*/
- protected $_output;
+ protected ?string $_output = null;
/**
- * @var string the PDF data as returned from getData()
+ * The PDF data as returned from getData()
*/
- protected $_data;
- protected $_data_utf8;
+ protected ?InfoFields $_data = null;
+ protected ?InfoFields $_data_utf8 = null;
/**
- * @var DataFields the PDF form field data as returned from getDataFields()
+ * The PDF form field data as returned from getDataFields()
*/
- protected $_dataFields;
- protected $_dataFields_utf8;
+ protected ?DataFields $_dataFields = null;
+ protected ?DataFields $_dataFields_utf8 = null;
/**
- * @var Pdf[]|null if the input was an instance, we keep a reference here,
+ * @var Pdf[] if the input was an instance, we keep a reference here,
* so that it won't get unlinked before this object gets destroyed
*/
- protected $_pdfs;
+ protected array $_pdfs = [];
/**
* @param string|Pdf|array $pdf a pdf filename or Pdf instance or an array
@@ -90,7 +90,7 @@ class Pdf
* @param array $options Options to pass to set on the Command instance,
* e.g. the pdftk binary path
*/
- public function __construct($pdf = null, $options = array())
+ public function __construct(string|Pdf|array|null $pdf = null, array $options = array())
{
$command = $this->getCommand();
if ($options !== array()) {
@@ -118,7 +118,7 @@ public function __construct($pdf = null, $options = array())
* @param string|null $password the owner (or user) password if any
* @return Pdf the pdf instance for method chaining
*/
- public function addFile($name, $handle = null, $password = null)
+ public function addFile(string|Pdf $name, ?string $handle = null, ?string $password = null): self
{
if ($handle === null || is_numeric($handle)) {
$handle = $this->nextHandle();
@@ -173,8 +173,13 @@ public function addFile($name, $handle = null, $password = null)
* @param string $rotation the rotation to apply to the pages.
* @return Pdf the pdf instance for method chaining
*/
- public function cat($start = null, $end = null, $handle = null, $qualifier = null, $rotation = null)
- {
+ public function cat(
+ int|string|array|null $start = null,
+ int|string|null $end = null,
+ ?string $handle = null,
+ ?string $qualifier = null,
+ ?string $rotation = null,
+ ): self {
$this->getCommand()
->setOperation('cat')
->addPageRange($start, $end, $handle, $qualifier, $rotation);
@@ -198,19 +203,24 @@ public function cat($start = null, $end = null, $handle = null, $qualifier = nul
*
* This will give the page order 1, 4, 3, 5, 2, 9 in the out.pdf
*
- * @param string $handle the handle of the input file to use
- * @param int|array $start the start page number or an array of page
+ * @param int|string|array $start the start page number or an array of page
* numbers.
- * @param int|null $end the end page number or null for single page (or
+ * @param int|string|null $end the end page number or null for single page (or
* list if $start is an array)
+ * @param string $handle the handle of the input file to use
* @param string|null $qualifier the page number qualifier, either 'even'
* or 'odd' or null for none
* @param string $rotation the rotation to apply to the pages. See cat()
* for more details.
* @return Pdf the pdf instance for method chaining
*/
- public function shuffle($start, $end = null, $handle = null, $qualifier = null, $rotation = null)
- {
+ public function shuffle(
+ int|string|array $start,
+ int|string|null $end = null,
+ ?string $handle = null,
+ ?string $qualifier = null,
+ ?string $rotation = null,
+ ): self {
$this->getCommand()
->setOperation('shuffle')
->addPageRange($start, $end, $handle, $qualifier, $rotation);
@@ -224,7 +234,7 @@ public function shuffle($start, $end = null, $handle = null, $qualifier = null,
* null for default 'pg_%04d.pdf'
* @return bool whether the burst operation was successful
*/
- public function burst($filepattern = null)
+ public function burst(?string $filepattern = null): bool
{
$this->constrainSingleFile();
$this->getCommand()->setOperation('burst');
@@ -238,9 +248,10 @@ public function burst($filepattern = null)
* @param array $files the list of full paths to the files to attach
* @param string $toPage the page to add the attachment to. If omitted the
* files are attached at the document level.
- * @return bool whether the operation was successful
+ *
+ * @return self whether the operation was successful
*/
- public function attachFiles($files, $toPage = null)
+ public function attachFiles(array $files, ?string $toPage = null): self
{
$this->constrainSingleFile();
if ($toPage !== null) {
@@ -259,7 +270,7 @@ public function attachFiles($files, $toPage = null)
* @param string|null $dir the output directory
* @return bool whether the operation was successful
*/
- public function unpackFiles($dir = null)
+ public function unpackFiles(?string $dir = null): bool
{
$this->constrainSingleFile();
$this->getCommand()->setOperation('unpack_files');
@@ -273,7 +284,7 @@ public function unpackFiles($dir = null)
* @param string $name name of the FDF file
* @return bool whether the pdf is generated successful
*/
- public function generateFdfFile($name)
+ public function generateFdfFile(string $name): bool
{
$this->constrainSingleFile();
$this->getCommand()->setOperation('generate_fdf');
@@ -294,8 +305,12 @@ public function generateFdfFile($name)
* give best results so you should not have to change the default.
* @return Pdf the pdf instance for method chaining
*/
- public function fillForm($data, $encoding = 'UTF-8', $dropXfa = true, $format = 'xfdf')
- {
+ public function fillForm(
+ string|array $data,
+ string $encoding = 'UTF-8',
+ bool $dropXfa = true,
+ string $format = 'xfdf',
+ ): self {
$this->constrainSingleFile();
if (is_array($data)) {
$className = '\mikehaertl\pdftk\\' . ($format === 'xfdf' ? 'XfdfFile' : 'FdfFile');
@@ -319,7 +334,7 @@ public function fillForm($data, $encoding = 'UTF-8', $dropXfa = true, $format =
* @param string the encoding of the data. Default is 'UTF-8'.
* @return Pdf the pdf instance for method chaining
*/
- public function updateInfo($data, $encoding = 'UTF-8')
+ public function updateInfo(string|array|InfoFields|InfoFile $data, string $encoding = 'UTF-8'): self
{
$this->constrainSingleFile();
if (is_array($data) || $data instanceof InfoFields) {
@@ -342,7 +357,7 @@ public function updateInfo($data, $encoding = 'UTF-8')
* is used.
* @return Pdf the pdf instance for method chaining
*/
- public function background($file)
+ public function background(string $file): self
{
$this->constrainSingleFile();
$this->getCommand()
@@ -361,7 +376,7 @@ public function background($file)
* @param string $file name of the background PDF file.
* @return Pdf the pdf instance for method chaining
*/
- public function multiBackground($file)
+ public function multiBackground(string $file): self
{
$this->getCommand()
->setOperation('multibackground')
@@ -378,7 +393,7 @@ public function multiBackground($file)
* first page is used.
* @return Pdf the pdf instance for method chaining
*/
- public function stamp($file)
+ public function stamp(string $file): self
{
$this->constrainSingleFile();
$this->getCommand()
@@ -397,7 +412,7 @@ public function stamp($file)
* @param string $file name of the PDF file to add as overlay
* @return Pdf the pdf instance for method chaining
*/
- public function multiStamp($file)
+ public function multiStamp(string $file): self
{
$this->getCommand()
->setOperation('multistamp')
@@ -410,7 +425,7 @@ public function multiStamp($file)
* true.
* @return InfoFields|bool meta data about the PDF or false on failure
*/
- public function getData($utf8 = true)
+ public function getData(bool $utf8 = true): InfoFields|bool
{
$property = $utf8 ? '_data_utf8' : '_data';
if ($this->$property === null) {
@@ -432,7 +447,7 @@ public function getData($utf8 = true)
* @return DataFields|bool data about the PDF form fields or false on
* failure
*/
- public function getDataFields($utf8 = true)
+ public function getDataFields(bool $utf8 = true): DataFields|bool
{
$property = $utf8 ? '_dataFields_utf8' : '_dataFields';
if ($this->$property === null) {
@@ -458,7 +473,7 @@ public function getDataFields($utf8 = true)
* ModifyAnnotations, FillIn, AllFeatures.
* @return Pdf the pdf instance for method chaining
*/
- public function allow($permissions = null)
+ public function allow($permissions = null): self
{
$this->getCommand()
->addOption('allow', $permissions, false);
@@ -470,7 +485,7 @@ public function allow($permissions = null)
*
* @return Pdf the pdf instance for method chaining
*/
- public function flatten()
+ public function flatten(): self
{
$this->getCommand()
->addOption('flatten');
@@ -484,7 +499,7 @@ public function flatten()
* compression
* @return Pdf the pdf instance for method chaining
*/
- public function compress($compress = true)
+ public function compress($compress = true): self
{
$this->getCommand()
->addOption($compress ? 'compress' : 'uncompress');
@@ -498,7 +513,7 @@ public function compress($compress = true)
* @param string $id, either 'first' (default) or 'last'
* @return Pdf the pdf instance for method chaining
*/
- public function keepId($id = 'first')
+ public function keepId($id = 'first'): self
{
$this->getCommand()
->addOption($id === 'first' ? 'keep_first_id' : 'keep_final_id');
@@ -515,7 +530,7 @@ public function keepId($id = 'first')
*
* @return Pdf the pdf instance for method chaining
*/
- public function needAppearances()
+ public function needAppearances(): self
{
$this->getCommand()
->addOption('need_appearances');
@@ -532,7 +547,7 @@ public function needAppearances()
*
* @return Pdf the pdf instance for method chaining
*/
- public function dropXfa()
+ public function dropXfa(): self
{
$this->getCommand()
->addOption('drop_xfa');
@@ -549,7 +564,7 @@ public function dropXfa()
*
* @return Pdf the pdf instance for method chaining
*/
- public function dropXmp()
+ public function dropXmp(): self
{
$this->getCommand()
->addOption('drop_xmp');
@@ -560,7 +575,7 @@ public function dropXmp()
* @param string $password the owner password to set on the output PDF
* @return Pdf the pdf instance for method chaining
*/
- public function setPassword($password)
+ public function setPassword(string $password): self
{
$this->getCommand()
->addOption('owner_pw', $password, true);
@@ -571,7 +586,7 @@ public function setPassword($password)
* @param string $password the user password to set on the output PDF
* @return Pdf the pdf instance for method chaining
*/
- public function setUserPassword($password)
+ public function setUserPassword(string $password): self
{
$this->getCommand()
->addOption('user_pw', $password, true);
@@ -599,7 +614,7 @@ public function passwordEncryption($strength = 128)
* @param string $fontName the path to the font or the name of a font family.
* @return Pdf the pdf instance for method chaining
*/
- public function replacementFont($path)
+ public function replacementFont(string $path): self
{
$this->getCommand()
->addOption('replacement_font', $path);
@@ -612,7 +627,7 @@ public function replacementFont($path)
* @param string $name of output file
* @return bool whether the PDF could be processed and saved
*/
- public function saveAs($name)
+ public function saveAs(string $name): bool
{
if (!$this->getCommand()->getExecuted() && !$this->execute()) {
return false;
@@ -641,7 +656,7 @@ public function saveAs($name)
* automatically created headers, `false` can also be used as header value.
* @return bool whether PDF was created successfully
*/
- public function send($filename = null, $inline = false, $headers = array())
+ public function send(?string $filename = null, bool $inline = false, array $headers = array()): bool
{
if (!$this->getCommand()->getExecuted() && !$this->execute()) {
return false;
@@ -656,7 +671,7 @@ public function send($filename = null, $inline = false, $headers = array())
* @return string|bool the PDF content as a string or `false` if the PDF
* wasn't created successfully.
*/
- public function toString()
+ public function toString(): string|bool
{
if (!$this->getCommand()->getExecuted() && !$this->execute()) {
return false;
@@ -667,7 +682,7 @@ public function toString()
/**
* @return Command the command instance that executes pdftk
*/
- public function getCommand()
+ public function getCommand(): Command
{
if ($this->_command === null) {
$this->_command = new Command();
@@ -678,7 +693,7 @@ public function getCommand()
/**
* @return File the temporary output file instance
*/
- public function getTmpFile()
+ public function getTmpFile(): File
{
if ($this->_tmpFile === null) {
$this->_tmpFile = new File('', '.pdf', self::TMP_PREFIX, $this->tempDir);
@@ -689,7 +704,7 @@ public function getTmpFile()
/**
* @return string the error message or an empty string if none
*/
- public function getError()
+ public function getError(): string
{
return $this->_error;
}
@@ -701,7 +716,7 @@ public function getError()
*
* @return bool whether the command was executed successfully
*/
- public function execute()
+ public function execute(): bool
{
$command = $this->getCommand();
if ($command->getExecuted()) {
@@ -725,7 +740,7 @@ public function execute()
/**
* Make sure, that only one file is present
*/
- protected function constrainSingleFile()
+ protected function constrainSingleFile(): void
{
if ($this->getCommand()->getFileCount() > 1) {
throw new \Exception('This operation can only process single files');
@@ -735,7 +750,7 @@ protected function constrainSingleFile()
/**
* @return string the next handle in the series A, B, C, ... Z, AA, AB...
*/
- protected function nextHandle()
+ protected function nextHandle(): string
{
// N.B. Multi-character handles are only available in pdftk 1.45+
diff --git a/src/XfdfFile.php b/src/XfdfFile.php
index ff75a16..af99556 100644
--- a/src/XfdfFile.php
+++ b/src/XfdfFile.php
@@ -54,18 +54,18 @@ class XfdfFile extends File
{
// XFDF file header
private const XFDF_HEADER = <<
-
-
+
+
+
-FDF;
+ FDF;
// XFDF file footer
private const XFDF_FOOTER = <<
-
+
+
-FDF;
+ FDF;
/**
* Constructor
@@ -79,8 +79,13 @@ class XfdfFile extends File
* created. Autodetected if not provided.
* @param string|null $encoding of the data. Default is 'UTF-8'.
*/
- public function __construct($data, $suffix = null, $prefix = null, $directory = null, $encoding = 'UTF-8')
- {
+ public function __construct(
+ array $data,
+ ?string $suffix = null,
+ ?string $prefix = null,
+ ?string $directory = null,
+ ?string $encoding = 'UTF-8',
+ ) {
if ($directory === null) {
$directory = self::getTempDir();
}
@@ -91,9 +96,7 @@ public function __construct($data, $suffix = null, $prefix = null, $directory =
$prefix = 'php_pdftk_xfdf_';
}
- $tempfile = tempnam($directory, $prefix);
- $this->_fileName = $tempfile . $suffix;
- rename($tempfile, $this->_fileName);
+ parent::__construct('', $suffix, $prefix, $directory);
$fields = $this->parseData($data, $encoding);
$this->writeXml($fields);
@@ -142,23 +145,27 @@ public function __construct($data, $suffix = null, $prefix = null, $directory =
* ],
* ]
*
- *
* @param mixed $data the data to parse
* @param string the encoding of the data
+ * @param null|string $encoding
+ *
* @return array the result array in UTF-8 encoding with dot keys converted
* to nested arrays
*/
- protected function parseData($data, $encoding)
+ protected function parseData($data, ?string $encoding): array
{
+ $willConvert = $encoding !== 'UTF-8' && function_exists('mb_convert_encoding');
$result = array();
foreach ($data as $key => $value) {
- if ($encoding !== 'UTF-8' && function_exists('mb_convert_encoding')) {
+ $key = (string) $key;
+ if ($willConvert) {
$key = mb_convert_encoding($key, 'UTF-8', $encoding);
$value = mb_convert_encoding($value, 'UTF-8', $encoding);
}
if (strpos($key, '.') === false) {
$result['_' . $key] = $value;
} else {
+ /** @var array $target */
$target = &$result;
$keyParts = explode('.', $key);
$lastPart = array_pop($keyParts);
@@ -166,6 +173,7 @@ protected function parseData($data, $encoding)
if (!isset($target['_' . $part])) {
$target['_' . $part] = array();
}
+ /** @var array $target */
$target = &$target['_' . $part];
}
$target['_' . $lastPart] = $value;
@@ -179,7 +187,7 @@ protected function parseData($data, $encoding)
*
* @param array $fields the fields in a nested array structure
*/
- protected function writeXml($fields)
+ protected function writeXml(array $fields): void
{
// Use fwrite, since file_put_contents() messes around with character encoding
$fp = fopen($this->_fileName, 'w');
@@ -192,11 +200,11 @@ protected function writeXml($fields)
/**
* Write the fields to the given filepointer
*
- * @param int $fp
+ * @param resource $fp
* @param mixed[] $fields an array of field values as returned by
* `parseData()`.
*/
- protected function writeFields($fp, $fields)
+ protected function writeFields($fp, array $fields): void
{
foreach ($fields as $key => $value) {
$key = $this->xmlEncode(substr($key, 1));
@@ -222,7 +230,7 @@ protected function writeFields($fp, $fields)
* @param string|null $value the value to encode
* @return string|null the value correctly encoded for use in a XML document
*/
- protected function xmlEncode($value)
+ protected function xmlEncode(?string $value): ?string
{
if ($value === null) {
return null;