<?php
 declare(strict_types=1); namespace SpomkyLabs\Pki\CryptoEncoding; use ArrayIterator; use Countable; use IteratorAggregate; use LogicException; use RuntimeException; use Stringable; use UnexpectedValueException; use function count; use function is_string; use const PREG_SET_ORDER; final class PEMBundle implements Countable, IteratorAggregate, Stringable { private array $pems; private function __construct(PEM ...$pems) { $this->pems = $pems; } public function __toString(): string { return $this->string(); } public static function create(PEM ...$pems): self { return new self(...$pems); } public static function fromString(string $str): self { $hasMatches = preg_match_all(PEM::PEM_REGEX, $str, $matches, PREG_SET_ORDER); if ($hasMatches === false || $hasMatches === 0) { throw new UnexpectedValueException('No PEM blocks.'); } $pems = array_map( static function ($match) { $payload = preg_replace('/\s+/', '', $match[2]); if (! is_string($payload)) { throw new UnexpectedValueException('Failed to decode PEM data.'); } $data = base64_decode($payload, true); if ($data === false) { throw new UnexpectedValueException('Failed to decode PEM data.'); } return PEM::create($match[1], $data); }, $matches ); return self::create(...$pems); } public static function fromFile(string $filename): self { if (! is_readable($filename)) { throw new RuntimeException("Failed to read {$filename}."); } $str = file_get_contents($filename); if ($str === false) { throw new RuntimeException("Failed to read {$filename}."); } return self::fromString($str); } public function withPEMs(PEM ...$pems): self { $obj = clone $this; $obj->pems = array_merge($obj->pems, $pems); return $obj; } public function all(): array { return $this->pems; } public function first(): PEM { if (count($this->pems) === 0) { throw new LogicException('No PEMs.'); } return $this->pems[0]; } public function last(): PEM { if (count($this->pems) === 0) { throw new LogicException('No PEMs.'); } return $this->pems[count($this->pems) - 1]; } public function count(): int { return count($this->pems); } public function getIterator(): ArrayIterator { return new ArrayIterator($this->pems); } public function string(): string { return implode("\n", array_map(static fn (PEM $pem) => $pem->string(), $this->pems)); } } 