Skip to content

Commit 5dc08c8

Browse files
authored
Import and modify Validator class from MakerBundle (#4)
* Import and modify Validator class from MakerBundle * Trim $ from field name * Trim spaces from field name
1 parent e59afbe commit 5dc08c8

3 files changed

Lines changed: 186 additions & 3 deletions

File tree

src/Maker/MakeDocument.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
88
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\DocumentClassGenerator;
99
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\MongoDBHelper;
10+
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\Validator;
1011
use Doctrine\ODM\MongoDB\Types\Type;
1112
use InvalidArgumentException;
1213
use ReflectionClass;
@@ -23,7 +24,6 @@
2324
use Symfony\Bundle\MakerBundle\Util\ClassDetails;
2425
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty;
2526
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
26-
use Symfony\Bundle\MakerBundle\Validator;
2727
use Symfony\Component\Console\Command\Command;
2828
use Symfony\Component\Console\Input\InputArgument;
2929
use Symfony\Component\Console\Input\InputInterface;
@@ -195,7 +195,7 @@ private function askForNextField(ConsoleStyle $io, array $fields, string $docume
195195
$questionText = 'Add another property? Enter the property name (or press <return> to stop adding fields)';
196196
}
197197

198-
$fieldName = $io->ask($questionText, null, function ($name) use ($fields) {
198+
$fieldName = $io->ask($questionText, null, static function ($name) use ($fields) {
199199
// allow it to be empty
200200
if (! $name) {
201201
return $name;
@@ -205,7 +205,7 @@ private function askForNextField(ConsoleStyle $io, array $fields, string $docume
205205
throw new InvalidArgumentException(sprintf('The "%s" property already exists.', $name));
206206
}
207207

208-
return Validator::validateDoctrineFieldName($name, $this->mongoDBHelper->getRegistry());
208+
return Validator::validateFieldName($name);
209209
});
210210

211211
if (! $fieldName) {

src/MongoDB/Validator.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\MongoDBMakerBundle\MongoDB;
6+
7+
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
8+
9+
use function preg_match;
10+
use function sprintf;
11+
use function str_contains;
12+
use function trim;
13+
14+
final class Validator
15+
{
16+
/**
17+
* Validates that a value is not blank.
18+
*/
19+
public static function notBlank(string|null $value = null): string
20+
{
21+
if ($value === null || $value === '') {
22+
throw new RuntimeCommandException('This value cannot be blank.');
23+
}
24+
25+
return $value;
26+
}
27+
28+
/**
29+
* Validates a MongoDB document field name.
30+
*
31+
* MongoDB field names:
32+
* - Cannot be empty
33+
* - $ prefix is trimmed (reserved for operators)
34+
* - Cannot contain null character
35+
* - Cannot contain dots (used for nested documents)
36+
* - Should be valid PHP property names
37+
*/
38+
public static function validateFieldName(string $name): string
39+
{
40+
// Trim $ prefix (reserved for MongoDB operators) and spaces
41+
$name = trim($name, '$ ');
42+
43+
if ($name === '') {
44+
throw new RuntimeCommandException('Field name cannot be empty.');
45+
}
46+
47+
// MongoDB-specific restrictions
48+
if (str_contains($name, '.')) {
49+
throw new RuntimeCommandException(sprintf('Field name "%s" cannot contain a dot (used for nested documents).', $name));
50+
}
51+
52+
if (str_contains($name, "\0")) {
53+
throw new RuntimeCommandException(sprintf('Field name "%s" cannot contain null characters.', $name));
54+
}
55+
56+
// Check for valid PHP property name (starts with letter or underscore, followed by letters, numbers, or underscores)
57+
if (! preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $name)) {
58+
throw new RuntimeCommandException(sprintf('"%s" is not a valid PHP property name.', $name));
59+
}
60+
61+
return $name;
62+
}
63+
}

tests/MongoDB/ValidatorTest.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\MongoDBMakerBundle\Tests\MongoDB;
6+
7+
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\Validator;
8+
use Generator;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\TestCase;
11+
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
12+
13+
class ValidatorTest extends TestCase
14+
{
15+
#[DataProvider('validNotBlankProvider')]
16+
public function testNotBlankWithValidValue(string $value): void
17+
{
18+
$result = Validator::notBlank($value);
19+
20+
$this->assertSame($value, $result);
21+
}
22+
23+
/** @return Generator<string, array{0: string}> */
24+
public static function validNotBlankProvider(): Generator
25+
{
26+
yield 'simple string' => ['valid'];
27+
yield 'string with spaces' => ['hello world'];
28+
yield 'single character' => ['a'];
29+
yield 'whitespace only' => [' '];
30+
}
31+
32+
#[DataProvider('invalidNotBlankProvider')]
33+
public function testNotBlankWithInvalidValue(string|null $value): void
34+
{
35+
$this->expectException(RuntimeCommandException::class);
36+
$this->expectExceptionMessage('This value cannot be blank.');
37+
38+
Validator::notBlank($value);
39+
}
40+
41+
/** @return Generator<string, array{0: string|null}> */
42+
public static function invalidNotBlankProvider(): Generator
43+
{
44+
yield 'null' => [null];
45+
yield 'empty string' => [''];
46+
}
47+
48+
#[DataProvider('validFieldNameProvider')]
49+
public function testValidateFieldNameWithValidNames(string $fieldName, string $expected): void
50+
{
51+
$result = Validator::validateFieldName($fieldName);
52+
53+
$this->assertSame($expected, $result);
54+
}
55+
56+
/** @return Generator<string, array{0: string, 1: string}> */
57+
public static function validFieldNameProvider(): Generator
58+
{
59+
yield 'simple name' => ['name', 'name'];
60+
yield 'camelCase' => ['firstName', 'firstName'];
61+
yield 'with underscore prefix' => ['_private', '_private'];
62+
yield 'with numbers' => ['field1', 'field1'];
63+
yield 'snake_case' => ['user_name', 'user_name'];
64+
yield 'uppercase' => ['CONSTANT', 'CONSTANT'];
65+
yield 'mixed case with numbers' => ['myField2Test', 'myField2Test'];
66+
yield 'dollar prefix is trimmed' => ['$field', 'field'];
67+
yield 'multiple dollar prefix is trimmed' => ['$$field', 'field'];
68+
}
69+
70+
#[DataProvider('emptyFieldNameProvider')]
71+
public function testValidateFieldNameWithEmptyValue(string $fieldName): void
72+
{
73+
$this->expectException(RuntimeCommandException::class);
74+
$this->expectExceptionMessage('Field name cannot be empty.');
75+
76+
Validator::validateFieldName($fieldName);
77+
}
78+
79+
/** @return Generator<string, array{0: string|null}> */
80+
public static function emptyFieldNameProvider(): Generator
81+
{
82+
yield 'empty string' => [''];
83+
yield 'dollar only' => ['$'];
84+
yield 'multiple dollars only' => ['$$$'];
85+
}
86+
87+
#[DataProvider('invalidPhpPropertyNameProvider')]
88+
public function testValidateFieldNameWithInvalidPhpPropertyNames(string $fieldName): void
89+
{
90+
$this->expectException(RuntimeCommandException::class);
91+
$this->expectExceptionMessageMatches('/is not a valid PHP property name/');
92+
93+
Validator::validateFieldName($fieldName);
94+
}
95+
96+
/** @return Generator<string, array{0: string}> */
97+
public static function invalidPhpPropertyNameProvider(): Generator
98+
{
99+
yield 'starts with number' => ['1field'];
100+
yield 'contains dash' => ['field-name'];
101+
yield 'contains space' => ['field name'];
102+
yield 'contains special char' => ['field@name'];
103+
}
104+
105+
#[DataProvider('mongoDbRestrictedFieldNameProvider')]
106+
public function testValidateFieldNameWithMongoDbRestrictions(string $fieldName, string $expectedMessagePattern): void
107+
{
108+
$this->expectException(RuntimeCommandException::class);
109+
$this->expectExceptionMessageMatches($expectedMessagePattern);
110+
111+
Validator::validateFieldName($fieldName);
112+
}
113+
114+
/** @return Generator<string, array{0: string, 1: string}> */
115+
public static function mongoDbRestrictedFieldNameProvider(): Generator
116+
{
117+
yield 'contains dot' => ['field.name', '/cannot contain a dot/'];
118+
yield 'null character' => ["field\0name", '/cannot contain null characters/'];
119+
}
120+
}

0 commit comments

Comments
 (0)