diff --git a/src/Format/Format.php b/src/Format/Format.php index 6250b1fb..da089d2b 100644 --- a/src/Format/Format.php +++ b/src/Format/Format.php @@ -15,6 +15,11 @@ final class Format implements FormatInterface { + /** + * Constant for a regular expression matching valid indents. + */ + private const INDENT_PATTERN = '/^[ \t]+$/'; + /** * @var int */ @@ -46,7 +51,7 @@ public function __construct(int $jsonEncodeOptions, string $indent, bool $hasFin )); } - if (1 !== \preg_match('/^[ \t]+$/', $indent)) { + if (1 !== \preg_match(self::INDENT_PATTERN, $indent)) { throw new \InvalidArgumentException(\sprintf( '"%s" is not a valid indent.', $indent @@ -72,4 +77,45 @@ public function hasFinalNewLine(): bool { return $this->hasFinalNewLine; } + + public function withJsonEncodeOptions(int $jsonEncodeOptions): FormatInterface + { + if (0 > $jsonEncodeOptions) { + throw new \InvalidArgumentException(\sprintf( + '"%s" is not valid options for json_encode().', + $jsonEncodeOptions + )); + } + + $mutated = clone $this; + + $mutated->jsonEncodeOptions = $jsonEncodeOptions; + + return $mutated; + } + + public function withIndent(string $indent): FormatInterface + { + if (1 !== \preg_match(self::INDENT_PATTERN, $indent)) { + throw new \InvalidArgumentException(\sprintf( + '"%s" is not a valid indent.', + $indent + )); + } + + $mutated = clone $this; + + $mutated->indent = $indent; + + return $mutated; + } + + public function withHasFinalNewLine(bool $hasFinalNewLine): FormatInterface + { + $mutated = clone $this; + + $mutated->hasFinalNewLine = $hasFinalNewLine; + + return $mutated; + } } diff --git a/src/Format/FormatInterface.php b/src/Format/FormatInterface.php index 3351fefc..0815074a 100644 --- a/src/Format/FormatInterface.php +++ b/src/Format/FormatInterface.php @@ -20,4 +20,24 @@ public function jsonEncodeOptions(): int; public function indent(): string; public function hasFinalNewLine(): bool; + + /** + * @param string $indent + * + * @throws \InvalidArgumentException + * + * @return FormatInterface + */ + public function withIndent(string $indent): self; + + /** + * @param int $jsonEncodeOptions + * + * @throws \InvalidArgumentException + * + * @return FormatInterface + */ + public function withJsonEncodeOptions(int $jsonEncodeOptions): self; + + public function withHasFinalNewLine(bool $hasFinalNewLine): self; } diff --git a/test/Unit/Format/FormatTest.php b/test/Unit/Format/FormatTest.php index a5f18a86..43ce79be 100644 --- a/test/Unit/Format/FormatTest.php +++ b/test/Unit/Format/FormatTest.php @@ -106,18 +106,8 @@ public function testConstructorSetsValues(string $indent, bool $hasFinalNewLine) public function providerJsonIndentAndFinalNewLine(): \Generator { - $indents = [ - ' ', - "\t", - ]; - - $hasFinalNewLines = [ - true, - false, - ]; - - foreach ($indents as $indent) { - foreach ($hasFinalNewLines as $hasFinalNewLine) { + foreach ($this->indents() as $indent) { + foreach ($this->hasFinalNewLines() as $hasFinalNewLine) { yield [ $indent, $hasFinalNewLine, @@ -125,4 +115,142 @@ public function providerJsonIndentAndFinalNewLine(): \Generator } } } + + public function testWithJsonEncodeOptionsRejectsInvalidJsonEncodeOptions(): void + { + $jsonEncodeOptions = -1; + + $format = new Format( + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ' ', + true + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf( + '"%s" is not valid options for json_encode().', + $jsonEncodeOptions + )); + + $format->withJsonEncodeOptions($jsonEncodeOptions); + } + + public function testWithIndentClonesFormatAndSetsJsonEncodeOptions(): void + { + $format = new Format( + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ' ', + true + ); + + $jsonEncodeOptions = 9000; + + $mutated = $format->withJsonEncodeOptions($jsonEncodeOptions); + + $this->assertInstanceOf(FormatInterface::class, $mutated); + $this->assertNotSame($format, $mutated); + $this->assertSame($jsonEncodeOptions, $mutated->jsonEncodeOptions()); + } + + /** + * @dataProvider providerInvalidIndent + * + * @param string $indent + */ + public function testWithIndentRejectsInvalidIndent(string $indent): void + { + $format = new Format( + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ' ', + true + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf( + '"%s" is not a valid indent.', + $indent + )); + + $format->withIndent($indent); + } + + /** + * @dataProvider providerIndent + * + * @param string $indent + */ + public function testWithIndentClonesFormatAndSetsIndent(string $indent): void + { + $format = new Format( + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ' ', + true + ); + + $mutated = $format->withIndent($indent); + + $this->assertInstanceOf(FormatInterface::class, $mutated); + $this->assertNotSame($format, $mutated); + $this->assertSame($indent, $mutated->indent()); + } + + public function providerIndent(): \Generator + { + foreach ($this->indents() as $key => $indent) { + yield $key => [ + $indent, + ]; + } + } + + /** + * @dataProvider providerHasFinalNewLine + * + * @param bool $hasFinalNewLine + */ + public function testWithHasFinalNewLineClonesFormatAndSetsFinalNewLine(bool $hasFinalNewLine): void + { + $format = new Format( + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ' ', + false + ); + + $mutated = $format->withHasFinalNewLine($hasFinalNewLine); + + $this->assertInstanceOf(FormatInterface::class, $mutated); + $this->assertNotSame($format, $mutated); + $this->assertSame($hasFinalNewLine, $mutated->hasFinalNewLine()); + } + + public function providerHasFinalNewLine(): \Generator + { + foreach ($this->hasFinalNewLines() as $key => $hasFinalNewLine) { + yield $key => [ + $hasFinalNewLine, + ]; + } + } + + /** + * @return string[] + */ + private function indents(): array + { + return [ + 'space' => ' ', + 'tab' => "\t", + ]; + } + + /** + * @return bool[] + */ + private function hasFinalNewLines(): array + { + return [ + 'yes' => true, + 'no' => false, + ]; + } }