From 9ce0b9399d7f396870efb415bf20a19177922b7f Mon Sep 17 00:00:00 2001 From: Moln Date: Wed, 4 Aug 2021 23:08:08 +0800 Subject: [PATCH 1/3] Update copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 62346b3..7f8cae9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Zend Framework Egg +Copyright (c) 2021 ZFEgg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8cbae2f0f06cd355cfb7ae693ef0828fbcf0219c Mon Sep 17 00:00:00 2001 From: Moln Date: Tue, 10 Aug 2021 14:11:14 +0800 Subject: [PATCH 2/3] Add transformer keyword --- composer.json | 1 + phpunit.xml | 4 +- src/ConfigProvider.php | 1 + src/Factory/TransformerResolverFactory.php | 24 +++ src/Factory/ValidatorFactory.php | 6 +- src/Opis/Helper.php | 10 ++ .../RemoveAdditionalProperties.php | 2 +- src/Opis/Keyword/SetValueTrait.php | 41 +++++ src/Opis/Keyword/TransformerKeyword.php | 64 ++++++++ src/Opis/{ => Keyword}/TypeCast.php | 22 +-- src/Opis/RemoveAdditionalPropertiesParser.php | 1 + src/Opis/Resolver/LaminasFilterResolver.php | 36 +++++ src/Opis/Resolver/RegisterTrait.php | 152 ++++++++++++++++++ src/Opis/Resolver/ResolverInterface.php | 18 +++ src/Opis/Resolver/TransformerResolver.php | 31 ++++ src/Opis/Transformer/LaminasTransformer.php | 29 ++++ src/Opis/Transformer/TransformerInterface.php | 17 ++ src/Opis/TransformersParser.php | 117 ++++++++++++++ src/Opis/TypeCastParser.php | 1 + test/Factory/ValidatorFactoryTest.php | 13 +- .../Resolver/LaminasFilterResolverTest.php | 30 ++++ .../Opis/Resolver/TransformerResolverTest.php | 48 ++++++ test/Opis/TransformersParserTest.php | 89 ++++++++++ test/SetupTrait.php | 23 +-- test/test.json | 32 +++- 25 files changed, 777 insertions(+), 35 deletions(-) create mode 100644 src/Factory/TransformerResolverFactory.php create mode 100644 src/Opis/Helper.php rename src/Opis/{ => Keyword}/RemoveAdditionalProperties.php (93%) create mode 100644 src/Opis/Keyword/SetValueTrait.php create mode 100644 src/Opis/Keyword/TransformerKeyword.php rename src/Opis/{ => Keyword}/TypeCast.php (80%) create mode 100644 src/Opis/Resolver/LaminasFilterResolver.php create mode 100644 src/Opis/Resolver/RegisterTrait.php create mode 100644 src/Opis/Resolver/ResolverInterface.php create mode 100644 src/Opis/Resolver/TransformerResolver.php create mode 100644 src/Opis/Transformer/LaminasTransformer.php create mode 100644 src/Opis/Transformer/TransformerInterface.php create mode 100644 src/Opis/TransformersParser.php create mode 100644 test/Opis/Resolver/LaminasFilterResolverTest.php create mode 100644 test/Opis/Resolver/TransformerResolverTest.php create mode 100644 test/Opis/TransformersParserTest.php diff --git a/composer.json b/composer.json index 7a6eaf0..8dd8df2 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "doctrine/orm": "^2.9", "laminas/laminas-diactoros": "^2.5", "laminas/laminas-eventmanager": "^3.0", + "laminas/laminas-filter": "^2.11", "laminas/laminas-servicemanager": "^3.7", "mezzio/mezzio-router": "^3.0", "phpspec/prophecy-phpunit": "^2.0", diff --git a/phpunit.xml b/phpunit.xml index a40230a..a9bf52b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,5 @@ - - + + ./src diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 98294f8..4d1de46 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -16,6 +16,7 @@ public function __invoke(): array 'factories' => [ ContentValidationMiddleware::class => ContentValidationMiddlewareFactory::class, Validator::class => Factory\ValidatorFactory::class, + Opis\Resolver\TransformerResolver::class => Factory\TransformerResolverFactory::class, ] ] ]; diff --git a/src/Factory/TransformerResolverFactory.php b/src/Factory/TransformerResolverFactory.php new file mode 100644 index 0000000..96c2296 --- /dev/null +++ b/src/Factory/TransformerResolverFactory.php @@ -0,0 +1,24 @@ +has(FilterPluginManager::class)) { + $resolver->registerNS('laminas', new LaminasFilterResolver($container->get(FilterPluginManager::class))); + } + + return $resolver; + } +} diff --git a/src/Factory/ValidatorFactory.php b/src/Factory/ValidatorFactory.php index ee5e079..ea1000c 100644 --- a/src/Factory/ValidatorFactory.php +++ b/src/Factory/ValidatorFactory.php @@ -9,6 +9,8 @@ use Zfegg\ContentValidation\Opis\Filter\DoctrineRecordExistsFilter; use Zfegg\ContentValidation\Opis\Filter\RecordExistsFilter; use Zfegg\ContentValidation\Opis\RemoveAdditionalPropertiesParser; +use Zfegg\ContentValidation\Opis\Resolver\TransformerResolver; +use Zfegg\ContentValidation\Opis\TransformersParser; use Zfegg\ContentValidation\Opis\TypeCastParser; class ValidatorFactory @@ -23,7 +25,9 @@ public function __invoke(ContainerInterface $container): Validator foreach ($parser->supportedDrafts() as $draft) { $parser->draft($draft) ->prependKeyword(new TypeCastParser()) - ->prependKeyword(new RemoveAdditionalPropertiesParser()); + ->prependKeyword(new RemoveAdditionalPropertiesParser()) + ->appendKeyword(new TransformersParser($container->get(TransformerResolver::class))) + ; } if (isset($config['resolvers'])) { diff --git a/src/Opis/Helper.php b/src/Opis/Helper.php new file mode 100644 index 0000000..c9323ef --- /dev/null +++ b/src/Opis/Helper.php @@ -0,0 +1,10 @@ +currentDataPath(); + $data = $context->rootData(); + + $target = $data; + foreach ($path as $key) { + if (is_object($target)) { + $target = &$target->{$key}; + } else { + $target = &$target[$key]; + } + } + + $target = $transform($target); + + $resetPath = []; + foreach (array_reverse($path) as $key) { + array_unshift($resetPath, $key); + $context->popDataPath(); + if ($context->currentDataType() != 'array') { + break; + } + } + + foreach ($resetPath as $key) { + $context->pushDataPath($key); + } + } +} diff --git a/src/Opis/Keyword/TransformerKeyword.php b/src/Opis/Keyword/TransformerKeyword.php new file mode 100644 index 0000000..7d0dafc --- /dev/null +++ b/src/Opis/Keyword/TransformerKeyword.php @@ -0,0 +1,64 @@ +filters = $filters; + } + + /** + * @inheritDoc + */ + public function validate(ValidationContext $context, Schema $schema): ?ValidationError + { + $type = $context->currentDataType(); + $data = $context->currentData(); + + foreach ($this->filters as $filter) { + if (! isset($filter->types[$type])) { + throw new UnresolvedFilterException($filter->name, $type, $schema, $context); + } + + $func = $filter->types[$type]; + + if ($filter->args) { + $args = (array)$filter->args->resolve($context->rootData(), $context->currentDataPath()); + $args += $context->globals(); + } else { + $args = $context->globals(); + } + + if ($func instanceof TransformerInterface) { + $data = $func->transform($data, $context, $schema->info(), $args); + } else { + $data = $func($data, ...$args); + } + } + + $this->setValue($context, fn() => $data); + + return null; + } +} diff --git a/src/Opis/TypeCast.php b/src/Opis/Keyword/TypeCast.php similarity index 80% rename from src/Opis/TypeCast.php rename to src/Opis/Keyword/TypeCast.php index d40520d..4222503 100644 --- a/src/Opis/TypeCast.php +++ b/src/Opis/Keyword/TypeCast.php @@ -2,7 +2,7 @@ declare(strict_types = 1); -namespace Zfegg\ContentValidation\Opis; +namespace Zfegg\ContentValidation\Opis\Keyword; use Opis\JsonSchema\Errors\ValidationError; use Opis\JsonSchema\Keyword; @@ -11,6 +11,8 @@ class TypeCast implements Keyword { + use SetValueTrait; + private string $type; public function __construct(string $type) @@ -23,23 +25,7 @@ public function __construct(string $type) */ public function validate(ValidationContext $context, Schema $schema): ?ValidationError { - $path = $context->currentDataPath(); - $data = $context->rootData(); - - $target = $data; - foreach ($path as $key) { - if (is_object($target)) { - $target = &$target->{$key}; - } else { - $target = &$target[$key]; - } - } - - $context->popDataPath(); - - $target = $this->castValue($target); - - $context->pushDataPath($key); + $this->setValue($context, [$this, 'castValue']); return null; } diff --git a/src/Opis/RemoveAdditionalPropertiesParser.php b/src/Opis/RemoveAdditionalPropertiesParser.php index a05f6b6..9294f89 100644 --- a/src/Opis/RemoveAdditionalPropertiesParser.php +++ b/src/Opis/RemoveAdditionalPropertiesParser.php @@ -8,6 +8,7 @@ use Opis\JsonSchema\Keyword; use Opis\JsonSchema\Parsers\KeywordParser; use Opis\JsonSchema\Parsers\SchemaParser; +use Zfegg\ContentValidation\Opis\Keyword\RemoveAdditionalProperties; class RemoveAdditionalPropertiesParser extends KeywordParser { diff --git a/src/Opis/Resolver/LaminasFilterResolver.php b/src/Opis/Resolver/LaminasFilterResolver.php new file mode 100644 index 0000000..9a56b7f --- /dev/null +++ b/src/Opis/Resolver/LaminasFilterResolver.php @@ -0,0 +1,36 @@ +filterManager = $filterManager; + } + + public function resolve(string $name, string $type): TransformerInterface + { + return new LaminasTransformer($this->filterManager, $name); + } + + /** + * @return TransformerInterface[]|null + */ + public function resolveAll(string $name): ?array + { + $types = ['string', 'number', 'boolean', 'integer']; + return array_fill_keys( + $types, + new LaminasTransformer($this->filterManager, $name) + ); + } +} diff --git a/src/Opis/Resolver/RegisterTrait.php b/src/Opis/Resolver/RegisterTrait.php new file mode 100644 index 0000000..55ff12e --- /dev/null +++ b/src/Opis/Resolver/RegisterTrait.php @@ -0,0 +1,152 @@ +separator = $separator; + } + + /** + * @inheritDoc + */ + public function resolve(string $name, string $type) + { + [$ns, $name] = $this->parseName($name); + + if (! $ns && isset($this->services[$name])) { + return $this->services[$name][$type] ?? null; + } + + if (isset($this->ns[$ns])) { + return $this->ns[$ns]->resolve($name, $type); + } + + return null; + } + + /** + * @inheritDoc + */ + public function resolveAll(string $name): ?array + { + [$ns, $name] = $this->parseName($name); + + if (! $ns && isset($this->services[$name])) { + return $this->services[$name]; + } + + if (isset($this->ns[$ns])) { + return $this->ns[$ns]->resolveAll($name); + } + + return null; + } + + /** + * @param object|callable $service + */ + public function register(string $type, string $name, $service): self + { + [$ns, $name] = $this->parseName($name); + + if (isset($this->ns[$ns])) { + return $this->ns[$ns]->register($type, $name, $service); + } + + $this->services[$name][$type] = $service; + + return $this; + } + + public function unregister(string $name, ?string $type = null): bool + { + [$ns, $name] = $this->parseName($name); + + if (isset($this->ns[$ns])) { + return $this->ns[$ns]->unregister($name, $type); + } + + if (! isset($this->services[$name])) { + return false; + } + + if ($type === null) { + unset($this->services[$name]); + + return true; + } + + if (isset($this->services[$name][$type])) { + unset($this->services[$name][$type]); + + return true; + } + + return false; + } + + /** + * @param callable|object $service + */ + public function registerMultipleTypes(string $name, $service, ?array $types = null): self + { + [$ns, $name] = $this->parseName($name); + + if (isset($this->ns[$ns])) { + return $this->ns[$ns]->registerMultipleTypes($name, $service, $types); + } + + $types = $types ?? Helper::JSON_ALL_TYPES; + + foreach ($types as $type) { + $this->services[$name][$type] = $service; + } + + return $this; + } + + public function registerNS(string $ns, ResolverInterface $resolver): self + { + $this->ns[$ns] = $resolver; + + return $this; + } + + public function unregisterNS(string $ns): bool + { + if (isset($this->ns[$ns])) { + unset($this->ns[$ns]); + + return true; + } + + return false; + } + + protected function parseName(string $name): array + { + $name = strtolower($name); + + if (strpos($name, $this->separator) === false) { + return [null, $name]; + } + + return explode($this->separator, $name, 2); + } +} diff --git a/src/Opis/Resolver/ResolverInterface.php b/src/Opis/Resolver/ResolverInterface.php new file mode 100644 index 0000000..99ed2aa --- /dev/null +++ b/src/Opis/Resolver/ResolverInterface.php @@ -0,0 +1,18 @@ +resolveAll2($name); + } +} diff --git a/src/Opis/Transformer/LaminasTransformer.php b/src/Opis/Transformer/LaminasTransformer.php new file mode 100644 index 0000000..911790f --- /dev/null +++ b/src/Opis/Transformer/LaminasTransformer.php @@ -0,0 +1,29 @@ +filters = $filterManager; + $this->name = $name; + } + + /** + * @inheritDoc + */ + public function transform($data, ValidationContext $context, SchemaInfo $info, array $args) + { + return $this->filters->get($this->name, $args)->filter($data); + } +} diff --git a/src/Opis/Transformer/TransformerInterface.php b/src/Opis/Transformer/TransformerInterface.php new file mode 100644 index 0000000..86e3876 --- /dev/null +++ b/src/Opis/Transformer/TransformerInterface.php @@ -0,0 +1,17 @@ +resolver = $resolver; + } + + public function type(): string + { + return self::TYPE_BEFORE; + } + + public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?Keyword + { + if (! $this->keywordExists($info)) { + return null; + } + + $transformers = $this->parseTransformers($parser, $this->keywordValue($info), $info); + if (! $transformers) { + return null; + } + + return new TransformerKeyword($transformers); + } + + /** + * @param string|array|object $transformers + */ + protected function parseTransformers( + SchemaParser $parser, + $transformers, + SchemaInfo $info + ): array { + if (is_string($transformers) || is_object($transformers)) { + $transformers = [$transformers]; + } + + if (is_array($transformers)) { + $list = []; + foreach ($transformers as $transformer) { + if ($transformer = $this->parseTransformer($parser, $transformer, $info)) { + $list[] = $transformer; + } + } + + return $list; + } + + throw $this->keywordException( + '{keyword} can be a non-empty string, an object or an array of string and objects', + $info + ); + } + + /** + * @param string|array|object $transformer + */ + protected function parseTransformer( + SchemaParser $parser, + $transformer, + SchemaInfo $info + ): ?object { + $vars = null; + if (is_object($transformer)) { + if (! property_exists($transformer, '$func') || ! is_string($transformer->{'$func'})) { + throw $this->keywordException('$func (for {keyword}) must be a non-empty string', $info); + } + + $vars = get_object_vars($transformer); + unset($vars['$func']); + + if (property_exists($transformer, '$vars')) { + if (! is_object($transformer->{'$vars'})) { + throw $this->keywordException('$vars (for {keyword}) must be a string', $info); + } + unset($vars['$vars']); + $vars = get_object_vars($transformer->{'$vars'}) + $vars; + } + + $transformer = $transformer->{'$func'}; + } elseif (! is_string($transformer) || ! $transformer) { + throw $this->keywordException( + '{keyword} can be a non-empty string, an object or an array of string and objects', + $info + ); + } + + $list = $this->resolver->resolveAll($transformer); + if (! $list) { + throw $this->keywordException("{keyword}: {$transformer} doesn't exists", $info); + } + + return (object)[ + 'name' => $transformer, + 'args' => $vars ? $this->createVariables($parser, $vars) : null, + 'types' => $list, + ]; + } +} diff --git a/src/Opis/TypeCastParser.php b/src/Opis/TypeCastParser.php index f076b5a..e97e08a 100644 --- a/src/Opis/TypeCastParser.php +++ b/src/Opis/TypeCastParser.php @@ -8,6 +8,7 @@ use Opis\JsonSchema\Keyword; use Opis\JsonSchema\Parsers\KeywordParser; use Opis\JsonSchema\Parsers\SchemaParser; +use Zfegg\ContentValidation\Opis\Keyword\TypeCast; class TypeCastParser extends KeywordParser { diff --git a/test/Factory/ValidatorFactoryTest.php b/test/Factory/ValidatorFactoryTest.php index b64da1c..a4d7d8e 100644 --- a/test/Factory/ValidatorFactoryTest.php +++ b/test/Factory/ValidatorFactoryTest.php @@ -17,13 +17,17 @@ public function testFactory(): void $validator = $this->container->get(Validator::class); $data = <<<'JSON' { - "name": "John Doe", + "name": "John Doe", + "transformer-string": "John Doe", + "transformer-array": "John Doe", + "transformer-object": "John Doe", "age": "18abcd", "unchecked": "sdf", "state": "0", "sub" : { "foo": "123" - } + }, + "list": [["1a"]] } JSON; $data = json_decode($data); @@ -32,7 +36,12 @@ public function testFactory(): void $this->assertTrue($result->isValid()); $this->assertTrue(18 === $data->age); $this->assertFalse($data->state); + $this->assertEquals('JOHN DOE', $data->name); + $this->assertEquals('JOHN DOE', $data->{"transformer-string"}); + $this->assertEquals('JOHN DOE', $data->{"transformer-array"}); + $this->assertEquals('JOHN DOE', $data->{"transformer-object"}); $this->assertEquals('bar', $data->sub->bar); + $this->assertEquals(1, $data->list[0][0]); $this->assertObjectNotHasAttribute('unchecked', $data); } } diff --git a/test/Opis/Resolver/LaminasFilterResolverTest.php b/test/Opis/Resolver/LaminasFilterResolverTest.php new file mode 100644 index 0000000..2fb41c4 --- /dev/null +++ b/test/Opis/Resolver/LaminasFilterResolverTest.php @@ -0,0 +1,30 @@ +container->get(TransformerResolver::class); + $transformer = $resolver->resolve('laminas::toInt', 'integer'); + $rs = $transformer->transform( + '123', + $this->createMock(ValidationContext::class), + $this->createMock(SchemaInfo::class), + [] + ); + $this->assertIsInt($rs); + } +} diff --git a/test/Opis/Resolver/TransformerResolverTest.php b/test/Opis/Resolver/TransformerResolverTest.php new file mode 100644 index 0000000..53e0d7d --- /dev/null +++ b/test/Opis/Resolver/TransformerResolverTest.php @@ -0,0 +1,48 @@ +register('string', 'test', $obj); + $resolver->registerMultipleTypes('test2', $obj2); + + $resolver2 = new TransformerResolver(); + $foo = new \stdClass(); + $resolver->registerNS('resolver2', $resolver2); + $resolver->registerNS('resolver3', $resolver2); + $resolver->registerMultipleTypes('resolver2::foo', $foo); + $resolver->register('integer', 'resolver2::foo2', $foo); + + $this->assertEquals($obj, $resolver->resolve('test', 'string')); + $this->assertCount(7, $resolver->resolveAll('test2')); + $this->assertEquals($obj2, $resolver->resolve('test2', 'string')); + $this->assertNull($resolver->resolve('test', 'integer')); + + $this->assertEquals($foo, $resolver->resolve('resolver2::foo', 'integer')); + + $this->assertTrue($resolver->unregister('test')); + $this->assertTrue($resolver->unregister('test2', 'string')); + $this->assertTrue($resolver->unregister('resolver2::foo2')); + $this->assertTrue($resolver->unregisterNS('resolver2')); + $this->assertTrue($resolver->unregisterNS('resolver3')); + + $this->assertNull($resolver->resolveAll('test')); + $this->assertNull($resolver->resolve('test2', 'string')); + $this->assertNull($resolver->resolve('resolver2::foo', 'string')); + + $this->assertFalse($resolver->unregister('undefined')); // call unregister undefined + $this->assertFalse($resolver->unregister('test2', 'string')); // re-call unregister + $this->assertFalse($resolver->unregisterNS('undefined resolver')); // re-call unregister + } +} diff --git a/test/Opis/TransformersParserTest.php b/test/Opis/TransformersParserTest.php new file mode 100644 index 0000000..39ff03b --- /dev/null +++ b/test/Opis/TransformersParserTest.php @@ -0,0 +1,89 @@ +expectException(InvalidKeywordException::class); + $validator = $this->container->get(Validator::class); + $data = <<<'JSON' +{ + "error-transformers": "John Doe" +} +JSON; + $data = json_decode($data); + $validator->validate( + $data, + <<container->get(Validator::class); + $data = <<<'JSON' +{ + "name": "John Doe" +} +JSON; + $data = json_decode($data); + $result = $validator->validate( + $data, + <<assertTrue($result->isValid()); + $this->assertEquals(md5("John Doe", true), $data->name); + } +} diff --git a/test/SetupTrait.php b/test/SetupTrait.php index ea16cd5..907a4c6 100644 --- a/test/SetupTrait.php +++ b/test/SetupTrait.php @@ -5,9 +5,9 @@ namespace ZfeggTest\ContentValidation; use Laminas\ServiceManager\ServiceManager; +use Laminas\Stdlib\ArrayUtils; use Opis\JsonSchema\Resolvers\FilterResolver; use Opis\JsonSchema\Validator; -use Zfegg\ContentValidation\ConfigProvider; use Zfegg\ContentValidation\ContentValidationMiddleware; trait SetupTrait @@ -17,13 +17,8 @@ trait SetupTrait public function setUp(): void { - $config = (new ConfigProvider())(); - $container = new ServiceManager($config['dependencies']); - $container->setService('fooFilter', fn() => true); - $container->setService('barFilter', fn() => true); - $container->setService( - 'config', - new \ArrayObject([ + $config = new \ArrayObject(ArrayUtils::merge( + [ 'zfegg' => [ContentValidationMiddleware::class => ['transform_object_to_array' => true]], Validator::class => [ 'resolvers' => [ @@ -39,8 +34,16 @@ public function setUp(): void 'fooNs' => new FilterResolver() ] ] - ]) - ); + ], + ArrayUtils::merge( + (new \Zfegg\ContentValidation\ConfigProvider())(), + (new \Laminas\Filter\ConfigProvider())() + ) + )); + $container = new ServiceManager($config['dependencies']); + $container->setService('fooFilter', fn() => true); + $container->setService('barFilter', fn() => true); + $container->setService('config', $config); $this->container = $container; } diff --git a/test/test.json b/test/test.json index e526bbe..b98d226 100644 --- a/test/test.json +++ b/test/test.json @@ -5,7 +5,28 @@ "name": { "type": "string", "minLength": 1, - "maxLength": 64 + "maxLength": 64, + "$transformers": [ + "laminas::StripTags", + { + "$func": "strtolower" + }, + "strtoupper" + ] + }, + "transformer-string": { + "type": "string", + "$transformers": "strtoupper" + }, + "transformer-array": { + "type": "string", + "$transformers": ["strtoupper"] + }, + "transformer-object": { + "type": "string", + "$transformers": { + "$func": "strtoupper" + } }, "age": { "type": "integer", @@ -28,6 +49,15 @@ } }, "required": ["foo", "bar"] + }, + "list": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer" + } + } } }, "required": ["name", "age"], From 21e6629bb8de3d1b01ac5f9384efb82f30be2b29 Mon Sep 17 00:00:00 2001 From: Moln Date: Tue, 10 Aug 2021 14:16:30 +0800 Subject: [PATCH 3/3] Fix PHPCS --- src/Opis/Helper.php | 4 ++-- src/Opis/Resolver/TransformerResolver.php | 1 - test/Opis/TransformersParserTest.php | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Opis/Helper.php b/src/Opis/Helper.php index c9323ef..a38c88a 100644 --- a/src/Opis/Helper.php +++ b/src/Opis/Helper.php @@ -1,10 +1,10 @@