Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Dto/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ protected function createProperty( int|string $name, array $propertyData, ICompo
$property->setPattern( $propertyData[ 'pattern' ] );
}

if( isset( $propertyData[ 'enum' ] ) )
{
$strict = $propertyData[ 'strict' ] ?? true;
$property->setEnum( $propertyData[ 'enum' ], $strict );
}

return $property;
}

Expand Down
17 changes: 17 additions & 0 deletions src/Dto/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ public function setPattern( string $pattern ): Property
return $this;
}

/**
* Set allowed values for enum validation.
*
* @param array $allowedValues Array of allowed values
* @param bool $strict Whether to use strict comparison (default: true)
* @return Property
*/

public function setEnum( array $allowedValues, bool $strict = true ): Property
{
$this->validators->remove( 'enum' );

$this->validators->add( 'enum', new Validation\IsInSet( $allowedValues, $strict ) );

return $this;
}
Comment on lines +200 to +207
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for IsInSet class definition
echo "=== Searching for IsInSet class definition ==="
rg -n --type=php "class IsInSet" || echo "No IsInSet class found"

echo ""
echo "=== Searching for any references to IsInSet ==="
rg -n --type=php "IsInSet" || echo "No IsInSet references found"

echo ""
echo "=== Listing all files in Validation namespace ==="
fd -e php . -x rg -l "namespace.*Validation" {} \; | sort

echo ""
echo "=== Checking existing validators ==="
fd -e php . -path "*/Validation/*" -type f

Repository: Neuron-PHP/dto

Length of output: 563


🏁 Script executed:

#!/bin/bash
# Find the Validation namespace location
echo "=== Searching for Validation namespace declarations ==="
rg -n "namespace.*Validation" --type php

echo ""
echo "=== Finding all PHP files in src directory ==="
fd -e php . src/

echo ""
echo "=== Checking structure of src/Validation or similar ==="
find src -type d -name "*[Vv]alidat*" 2>/dev/null

echo ""
echo "=== Searching for other validators pattern (e.g., class Is*) ==="
rg -n "class Is[A-Z]" --type php

Repository: Neuron-PHP/dto

Length of output: 579


🏁 Script executed:

#!/bin/bash
# Read the Validation.php file to understand the structure
echo "=== Content of src/Dto/Validation.php ==="
cat -n src/Dto/Validation.php

echo ""
echo "=== Check how IsInSet is used in Property.php around line 204 ==="
sed -n '190,210p' src/Dto/Property.php | cat -n

Repository: Neuron-PHP/dto

Length of output: 1363


🏁 Script executed:

#!/bin/bash
# Search for other validator usage patterns in Property.php
echo "=== Searching for validator usage patterns ==="
rg -n "validators->add" --type php -B 2 -A 2

echo ""
echo "=== Searching for Validation namespace usage ==="
rg -n "Validation\\\\" --type php

echo ""
echo "=== Check what other setters look like ==="
sed -n '150,250p' src/Dto/Property.php | grep -A 10 "public function set"

Repository: Neuron-PHP/dto

Length of output: 4574


🏁 Script executed:

#!/bin/bash
# Search for where the validators are defined
echo "=== Searching for IsStringLength definition ==="
rg -n "class IsStringLength" --type php

echo ""
echo "=== Searching for IsRegExPattern definition ==="
rg -n "class IsRegExPattern" --type php

echo ""
echo "=== Searching for imports or use statements in Property.php ==="
head -20 src/Dto/Property.php

echo ""
echo "=== Check what Validation\Collection is ==="
rg -n "class Collection" --type php -A 5

Repository: Neuron-PHP/dto

Length of output: 976


🏁 Script executed:

#!/bin/bash
# The validators are from Neuron\Validation namespace, let's check for external dependencies
echo "=== Checking for composer.json ==="
cat composer.json 2>/dev/null || echo "No composer.json found"

echo ""
echo "=== Checking for any package/vendor references ==="
find . -name "composer.lock" -o -name "vendor" -type d 2>/dev/null

echo ""
echo "=== Searching for all Validation\Is* usage in Property.php ==="
grep -n "Validation\\\\Is" src/Dto/Property.php

Repository: Neuron-PHP/dto

Length of output: 2250


Missing IsInSet validator in external dependency.

Line 204 uses Validation\IsInSet, which should come from the external neuron-php/validation package (currently required as ^0.7.0). The class does not exist in this repository because validators are provided by that dependency, not implemented locally. Verify that the installed version of neuron-php/validation includes the IsInSet class, or update the package version if this validator was added in a later release.

🧰 Tools
🪛 GitHub Actions: CI

[error] 204-204: Class "Neuron\Validation\IsInSet" not found during Enum validation.


/**
* @return mixed
*/
Expand Down
312 changes: 312 additions & 0 deletions tests/Dto/EnumValidationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
<?php

use Neuron\Dto\Factory;
use PHPUnit\Framework\TestCase;

class EnumValidationTest extends TestCase
{
public function testEnumValidationPassesWithValidValue()
{
$properties = [
'role' => [
'type' => 'string',
'required' => true,
'enum' => ['admin', 'editor', 'author', 'subscriber']
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Test each valid value
$dto->role = 'admin';
$dto->validate();
$this->assertEquals( 'admin', $dto->role );

$dto->role = 'editor';
$dto->validate();
$this->assertEquals( 'editor', $dto->role );

$dto->role = 'subscriber';
$dto->validate();
$this->assertEquals( 'subscriber', $dto->role );
}

public function testEnumValidationFailsWithInvalidValue()
{
$properties = [
'status' => [
'type' => 'string',
'required' => true,
'enum' => ['active', 'inactive', 'pending']
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

try {
$dto->status = 'invalid_status';
$dto->validate();
$this->fail( 'Expected validation exception for invalid enum value' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
$this->assertStringContainsString( 'validation failed', $e->errors[0] );
}
}

public function testEnumValidationIsCaseSensitive()
{
$properties = [
'priority' => [
'type' => 'string',
'required' => true,
'enum' => ['low', 'medium', 'high']
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Valid lowercase
$dto->priority = 'low';
$dto->validate();
$this->assertEquals( 'low', $dto->priority );

// Invalid uppercase
try {
$dto->priority = 'LOW';
$dto->validate();
$this->fail( 'Expected validation exception for case mismatch' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}

public function testEnumWithIntegerValues()
{
$properties = [
'level' => [
'type' => 'integer',
'required' => true,
'enum' => [1, 2, 3, 4, 5]
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Valid integer
$dto->level = 3;
$dto->validate();
$this->assertEquals( 3, $dto->level );

// Invalid integer
try {
$dto->level = 10;
$dto->validate();
$this->fail( 'Expected validation exception for invalid enum value' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}

public function testEnumWithLooseComparison()
{
$properties = [
'code' => [
'type' => 'string',
'required' => true,
'enum' => ['low', 'medium', 'high'],
'strict' => false
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// With strict mode defaulting to true, this should work
$dto->code = 'low';
$dto->validate();
$this->assertEquals( 'low', $dto->code );

// Note: Loose comparison primarily matters for mixed-type scenarios
// but since we have type validators (IsString, IsInteger), the type
// check happens first, so loose comparison has limited use in DTOs
}

public function testEnumWithStrictComparison()
{
$properties = [
'id' => [
'type' => 'integer',
'required' => true,
'enum' => [1, 2, 3],
'strict' => true
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Valid integer
$dto->id = 1;
$dto->validate();
$this->assertEquals( 1, $dto->id );

// With strict comparison, string '1' should NOT match integer 1
try {
$dto->id = '1';
$dto->validate();
$this->fail( 'Expected validation exception for type mismatch in strict mode' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}

public function testEnumLoadedFromYamlFile()
{
// Create a temporary YAML file
$yamlContent = <<<YAML
dto:
role:
type: string
required: true
enum: ['admin', 'editor', 'author', 'subscriber']
YAML;

$tempFile = sys_get_temp_dir() . '/test_enum_' . uniqid() . '.yaml';
file_put_contents( $tempFile, $yamlContent );

try {
$factory = new Factory( $tempFile );
$dto = $factory->create();

// Valid value
$dto->role = 'admin';
$dto->validate();
$this->assertEquals( 'admin', $dto->role );

// Invalid value
try {
$dto->role = 'moderator';
$dto->validate();
$this->fail( 'Expected validation exception for invalid enum value' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
} finally {
// Clean up
if ( file_exists( $tempFile ) ) {
unlink( $tempFile );
}
}
}

public function testEnumWithOptionalProperty()
{
$properties = [
'category' => [
'type' => 'string',
'required' => false,
'enum' => ['news', 'sports', 'technology']
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Validation should pass when optional property is not set
$dto->validate();

// But if set, it must be a valid enum value
$dto->category = 'news';
$dto->validate();
$this->assertEquals( 'news', $dto->category );

// Invalid value should fail
try {
$dto->category = 'politics';
$dto->validate();
$this->fail( 'Expected validation exception for invalid enum value' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}

public function testEnumInNestedObject()
{
$properties = [
'user' => [
'type' => 'object',
'required' => true,
'properties' => [
'username' => [
'type' => 'string',
'required' => true
],
'role' => [
'type' => 'string',
'required' => true,
'enum' => ['admin', 'user', 'guest']
]
]
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Valid nested enum
$dto->user->username = 'johndoe';
$dto->user->role = 'admin';
$dto->validate();
$this->assertEquals( 'admin', $dto->user->role );

// Invalid nested enum
try {
$dto->user->role = 'superadmin';
$dto->validate();
$this->fail( 'Expected validation exception for invalid enum in nested object' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}

public function testMultipleEnumProperties()
{
$properties = [
'status' => [
'type' => 'string',
'required' => true,
'enum' => ['draft', 'published', 'archived']
],
'priority' => [
'type' => 'string',
'required' => true,
'enum' => ['low', 'medium', 'high']
]
];

$factory = new Factory( $properties );
$dto = $factory->create();

// Both valid
$dto->status = 'published';
$dto->priority = 'high';
$dto->validate();

$this->assertEquals( 'published', $dto->status );
$this->assertEquals( 'high', $dto->priority );

// One invalid
try {
$dto->status = 'deleted'; // Invalid
$dto->priority = 'high'; // Valid
$dto->validate();
$this->fail( 'Expected validation exception' );
} catch ( \Neuron\Core\Exceptions\Validation $e ) {
$this->assertNotEmpty( $e->errors );
}
}
}