diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results index 99a6ef7..f11f773 100644 --- a/.phpunit.cache/test-results +++ b/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":"pest_2.34.4","defects":[],"times":{"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_a_menu_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_API_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_without_model_option":0.003,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_has_make_action_command":0.004,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_with_model_option":0.002,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.136,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.141,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.099,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.003,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.066,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.069,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.129,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.065,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.094,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.062,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.124,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.001}} \ No newline at end of file +{"version":"pest_2.34.4","defects":[],"times":{"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_a_menu_action":0.002,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_API_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_without_model_option":0.002,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_has_make_action_command":0.004,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_with_model_option":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.136,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.141,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.099,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.003,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.066,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.069,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.124,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.063,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.093,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.062,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.122,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.001}} \ No newline at end of file diff --git a/src/ResourceAction.php b/src/ResourceAction.php index aa85679..929157a 100644 --- a/src/ResourceAction.php +++ b/src/ResourceAction.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; use Illuminate\Support\Facades\Validator; abstract class ResourceAction implements Execute @@ -71,113 +72,23 @@ public function __construct(array $inputs = []) abstract public function rules(): array; /** - * Set input data. + * Generic setter for properties. * - * @param array $inputs - * @return $this - */ - public function setInputs(array $inputs): self - { - $this->inputs = $inputs; - return $this; - } - - /** - * Set fields for constraint-based operations. - * - * @param array $constrainedBy + * @param string $property + * @param array $value * @return $this + * @throws ActionException */ - public function setConstrainedBy(array $constrainedBy): self - { - $this->constrainedBy = $constrainedBy; - return $this; - } - - /** - * Get constrained fields. - * - * @return array - */ - public function getConstrainedBy(): array - { - return $this->constrainedBy; - } - - /** - * Check if constraints are applied. - * - * @return bool - */ - public function hasConstrained(): bool - { - return count($this->constrainedBy) > 0; - } - - /** - * Set fields to hash before saving. - * - * @param array $hashFields - * @return $this - */ - public function setHashFields(array $hashFields): self - { - $this->hashFields = $hashFields; - return $this; - } - - /** - * Get fields to hash. - * - * @return array - */ - public function getHashFields(): array - { - return $this->hashFields; - } - - /** - * Check if any fields are set to be hashed. - * - * @return bool - */ - public function hasHashFields(): bool + public function setProperty(string $property, array $value): self { - return count($this->getHashFields()) > 0; - } + if (!property_exists($this, $property)) { + throw new ActionException("Property {$property} does not exist."); + } - /** - * Set fields to encrypt before saving. - * - * @param array $encryptFields - * @return $this - */ - public function setEncryptFields(array $encryptFields): self - { - $this->encryptFields = $encryptFields; + $this->{$property} = $value; return $this; } - /** - * Get fields to encrypt. - * - * @return array - */ - public function getEncryptFields(): array - { - return $this->encryptFields; - } - - /** - * Check if any fields are set to be encrypted. - * - * @return bool - */ - public function hasEncryptFields(): bool - { - return count($this->getEncryptFields()) > 0; - } - /** * Retrieve the current record. * @@ -203,23 +114,10 @@ public function inputs(): array * * @return void */ - public function hashFields(): void + protected function transformFields(): void { - if ($this->hasHashFields()) { - $this->applyTransformationOnFields($this->getHashFields(), fn ($value) => Hash::make($value)); - } - } - - /** - * Encrypt specified fields in the inputs or constraints. - * - * @return void - */ - public function encryptFields(): void - { - if ($this->hasEncryptFields()) { - $this->applyTransformationOnFields($this->getEncryptFields(), fn ($value) => encrypt($value)); - } + $this->applyTransformationOnFields($this->hashFields, fn($value) => Hash::make($value)); + $this->applyTransformationOnFields($this->encryptFields, fn($value) => encrypt($value)); } /** @@ -229,21 +127,7 @@ public function encryptFields(): void */ public function removeConfirmationFields(): void { - $this->inputs = array_filter($this->inputs, fn ($value, $key) => ! str($key)->contains('_confirmation'), ARRAY_FILTER_USE_BOTH); - } - - /** - * Validate inputs against defined rules. - * - * @throws \Illuminate\Validation\ValidationException - * @return void - */ - protected function validateInputs(): void - { - Validator::make( - array_merge($this->constrainedBy, $this->inputs), - $this->rules() - )->validate(); + $this->inputs = array_filter($this->inputs, fn($value, $key) => !Str::contains($key, '_confirmation'), ARRAY_FILTER_USE_BOTH); } /** @@ -255,23 +139,14 @@ protected function validateInputs(): void */ protected function applyTransformationOnFields(array $fields, callable $transformation): void { - if ($this->hasConstrained()) { - $constrainedBy = $this->constrainedBy; - foreach ($fields as $field) { - if (isset($constrainedBy[$field])) { - $constrainedBy[$field] = $transformation($constrainedBy[$field]); - } + $transformFields = function (&$value, $key) use ($fields, $transformation) { + if (in_array($key, $fields, true)) { + $value = $transformation($value); } - $this->constrainedBy = $constrainedBy; - } + }; - $inputs = $this->inputs; - foreach ($fields as $field) { - if (isset($inputs[$field])) { - $inputs[$field] = $transformation($inputs[$field]); - } - } - $this->inputs = $inputs; + array_walk_recursive($this->inputs, $transformFields); + array_walk_recursive($this->constrainedBy, $transformFields); } /** @@ -303,22 +178,38 @@ public function prepare(): void // Placeholder for child classes to implement custom preparation. } + /** + * Validates the inputs against the defined rules. + * + * @throws \Illuminate\Validation\ValidationException + * @return void + */ + protected function validateInputs(): void + { + Validator::make( + array_merge($this->constrainedBy, $this->inputs), + $this->rules() + )->validate(); + } + + /** * Execute the action with preparation, validation, and data processing. * * @return Model * @throws \Illuminate\Validation\ValidationException */ - public function execute() + public function execute(): Model { $this->prepare(); $this->validateInputs(); - $this->hashFields(); - $this->encryptFields(); + $this->transformFields(); $this->removeConfirmationFields(); - return $this->record = DB::transaction(fn () => $this->hasConstrained() - ? $this->model()::updateOrCreate($this->constrainedBy, $this->inputs) - : $this->model()::create($this->inputs)); + return $this->record = DB::transaction(function () { + return !empty($this->constrainedBy) + ? $this->model()::updateOrCreate($this->constrainedBy, $this->inputs) + : $this->model()::create($this->inputs); + }); } } diff --git a/tests/ResourceActionTest.php b/tests/ResourceActionTest.php index 9795710..aa33c57 100644 --- a/tests/ResourceActionTest.php +++ b/tests/ResourceActionTest.php @@ -27,7 +27,7 @@ expect($record)->toBeInstanceOf(User::class) ->and($record->name)->toBe('John Doe') ->and($record->email)->toBe('johndoe@example.com') - ->and(Hash::check('secretpassword', $record->password))->toBeTrue(); // Check if the password is hashed + ->and(Hash::check('secretpassword', $record->password))->toBeTrue(); }); // it validates required fields @@ -54,9 +54,8 @@ ]; // Stub a class without a model definition - $stubAction = new class($inputs) extends CreateUserAction - { - protected string $model = ''; // Not setting the model on purpose + $stubAction = new class($inputs) extends CreateUserAction { + protected string $model = ''; // Intentionally leave model empty }; // Act & Assert @@ -73,13 +72,13 @@ ]; $action = new CreateUserAction($inputs); - $action->setHashFields(['password']); + $action->setProperty('hashFields', ['password']); // Use setProperty to define hash fields // Act $record = $action->execute(); // Assert - expect(Hash::check('secretpassword', $record->password))->toBeTrue(); // Verify password hash + expect(Hash::check('secretpassword', $record->password))->toBeTrue(); }); // it removes confirmation fields from inputs @@ -113,7 +112,7 @@ $action = new CreateUserAction($inputs); - // // Mock the database connection and query builder + // Mock the database connection and query builder $mockConnection = Mockery::mock(); $mockQueryBuilder = Mockery::mock(); @@ -121,8 +120,8 @@ $mockConnection->shouldReceive('table')->andReturn($mockQueryBuilder); $mockQueryBuilder->shouldReceive('useWritePdo')->andReturn($mockQueryBuilder); $mockQueryBuilder->shouldReceive('where')->andReturn($mockQueryBuilder); - $mockQueryBuilder->shouldReceive('count')->andReturn(0); // Return a count of 0 for the test - $mockQueryBuilder->shouldReceive('updateOrCreate')->andReturn(Mockery::mock(User::class)); // Mock the final result + $mockQueryBuilder->shouldReceive('count')->andReturn(0); + $mockQueryBuilder->shouldReceive('updateOrCreate')->andReturn(Mockery::mock(User::class)); // Mock the transaction flow DB::shouldReceive('connection')->andReturn($mockConnection); @@ -139,6 +138,7 @@ // it applies encryption to specified fields it('applies encryption to specified fields', function () { + // Arrange $inputs = [ 'name' => 'John Doe', 'email' => 'johndoe@example.com', @@ -147,9 +147,7 @@ ]; $action = new CreateUserAction($inputs); - - // Assuming we want to encrypt the 'ssn' field - $action->setEncryptFields(['ssn']); + $action->setProperty('encryptFields', ['ssn']); // Use setProperty to define encryption fields // Act $record = $action->execute(); @@ -159,6 +157,5 @@ // Ensure 'ssn' field was encrypted $decryptedSSN = decrypt($record->ssn); - expect($decryptedSSN)->toBe('123-45-6789'); });