Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ will change already existing class property's value **AND access level**
#### addArrayPropertyItem

Add new item to the `array` class property. Will add new property in case it does not exist yet.

#### removeArrayPropertyItem

Remove items from the `array` class property.
Comment on lines +38 to +40
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The documentation for removeArrayPropertyItem is incomplete. It should clarify that if the specified property doesn't exist, the operation is silently skipped (no error is thrown and no property is created). Consider adding: "Remove items from the array class property. If the property does not exist, no action is taken."

Copilot uses AI. Check for mistakes.
8 changes: 8 additions & 0 deletions src/PHPFileBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use RonasIT\Larabuilder\Enums\AccessModifierEnum;
use RonasIT\Larabuilder\Exceptions\InvalidPHPFileException;
use RonasIT\Larabuilder\Visitors\AddArrayPropertyItem;
use RonasIT\Larabuilder\Visitors\RemoveArrayPropertyItem;
use RonasIT\Larabuilder\Visitors\SetPropertyValue;

class PHPFileBuilder
Expand Down Expand Up @@ -48,6 +49,13 @@ public function addArrayPropertyItem(string $propertyName, mixed $value): self
return $this;
}

public function removeArrayPropertyItem(string $propertyName, array $value): self
{
$this->traverser->addVisitor(new RemoveArrayPropertyItem($propertyName, $value));

return $this;
}

public function save(): void
{
$this->traverser->addVisitor(new CloningVisitor());
Expand Down
94 changes: 94 additions & 0 deletions src/Visitors/RemoveArrayPropertyItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace RonasIT\Larabuilder\Visitors;

use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Property;
use RonasIT\Larabuilder\Exceptions\UnexpectedPropertyTypeException;

class RemoveArrayPropertyItem extends SetPropertyValue
Copy link
Collaborator

Choose a reason for hiding this comment

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

it would be better to implement new abstract class PropertyVisitor class and use it as parent of this one and SetPropertyValue visitor

{
protected array $valuesToRemove;

public function __construct(
protected string $name,
array $valuesToRemove,
) {
$this->valuesToRemove = array_map(
callback: fn ($value) => $this->getPropertyValue($value)[0],
array: $valuesToRemove,
);

parent::__construct($name, []);
}
Comment on lines +17 to +27
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The method signature and implementation may be confusing for associative arrays. When calling removeArrayPropertyItem('prop', ['key' => [1, 2]]), the 'key' is ignored and only the value [1, 2] is used for matching. This means any entry in the array with value [1, 2] will be removed regardless of its key. While this works, it could be unintuitive for users who expect the key to be considered. Consider either documenting this behavior clearly or modifying the implementation to respect keys when they are provided in the removal specification.

Copilot uses AI. Check for mistakes.

/** @param Property $node */
protected function updateNode(Node $node): void
{
$arrayProperty = $node->props[0]->default;

if (!$arrayProperty instanceof Array_) {
throw new UnexpectedPropertyTypeException(
property: $this->name,
expectedType: 'array',
actualType: (is_null($node->type)) ? 'null' : (string) $node->type,
Copy link
Collaborator

Choose a reason for hiding this comment

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

it would be better to allow to receive null in exception

Suggested change
actualType: (is_null($node->type)) ? 'null' : (string) $node->type,
actualType: $node->type,

);
}

$newItems = [];

foreach ($arrayProperty->items as $item) {
if (!$this->shouldRemoveItem($item)) {
$newItems[] = $item;
}
}

$arrayProperty->items = $newItems;
}

protected function areNodesEqual(Node $expected, Node $actual): bool
{
return match (true) {
$expected instanceof Scalar && $actual instanceof Scalar => $expected->value === $actual->value,
$expected instanceof ConstFetch && $actual instanceof ConstFetch => $expected->name->name === $actual->name->name,
$expected instanceof Array_ && $actual instanceof Array_ => $this->areArrayNodesEqual($expected, $actual),
default => false,
};
Comment on lines +50 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

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

could we use something like this?

Suggested change
return match (true) {
$expected instanceof Scalar && $actual instanceof Scalar => $expected->value === $actual->value,
$expected instanceof ConstFetch && $actual instanceof ConstFetch => $expected->name->name === $actual->name->name,
$expected instanceof Array_ && $actual instanceof Array_ => $this->areArrayNodesEqual($expected, $actual),
default => false,
};
return $this->getPropertyValue($expected) === $this->getPropertyValue($actual);

}

protected function areArrayNodesEqual(Array_ $expected, Array_ $actual): bool
{
if (count($expected->items) !== count($actual->items)) {
return false;
}

foreach ($expected->items as $index => $expectedItem) {
$actualItem = $actual->items[$index];

if (!$this->areNodesEqual($expectedItem->value, $actualItem->value)) {
return false;
}
}

return true;
}

protected function shouldRemoveItem(Node $item): bool
{
foreach ($this->valuesToRemove as $removeValue) {
if ($this->areNodesEqual($item->value, $removeValue)) {
return true;
}
}

return false;
}

protected function insertNode(Node $node): Node
{
return $node;
}
}
44 changes: 44 additions & 0 deletions tests/PHPFileBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,50 @@ public function testSetPropertyInTrait(): void
->addArrayPropertyItem('tags', 'three')
->addArrayPropertyItem('tags', 4)
->setProperty('newString', 'some string')
->removeArrayPropertyItem('fillable', ['name'])
->save();
}

public function testRemoveArrayPropertyItem(): void
{
$this->mockNativeFunction(
'RonasIT\Larabuilder',
$this->callFileGetContent('some_file_path.php', 'class_with_array_properties.php'),
$this->callFilePutContent('some_file_path.php', 'class_with_array_properties_removed.php'),
);

(new PHPFileBuilder('some_file_path.php'))
->removeArrayPropertyItem('fillable', ['name', 'age'])
->removeArrayPropertyItem('tags', ['two', 3, 5.5, true])
->removeArrayPropertyItem('newMultiArrayProperty', ['arrayProperty' => [0 => 1, 1 => 'string', 2 => true]])
->removeArrayPropertyItem('newMultiArrayProperty', ['arrayProperty2' => [1]])
->save();
}

public function testRemoveArrayPropertyItemThrowsException(): void
{
$this->mockNativeFunction(
'RonasIT\Larabuilder',
$this->callFileGetContent('some_file_path.php', 'class_with_array_properties.php'),
);

$this->assertExceptionThrew(UnexpectedPropertyTypeException::class, "Property 'notArray' has unexpected type. Expected 'array', actual 'bool'.");

(new PHPFileBuilder('some_file_path.php'))
->removeArrayPropertyItem('notArray', ['value'])
->save();
}

public function testRemoveArrayPropertyItemNotProperty(): void
{
$this->mockNativeFunction(
'RonasIT\Larabuilder',
$this->callFileGetContent('some_file_path.php', 'class_without_properties.php'),
$this->callFilePutContent('some_file_path.php', 'class_without_properties_unchanged.php'),
);

(new PHPFileBuilder('some_file_path.php'))
->removeArrayPropertyItem('notProperty', ['value'])
->save();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
class SomeClass
{
public string $stringProperty = 'some value';
protected array $tags = ['one', 'two'];
protected array $tags = ['one', 'two', 3, true, 5.5, 78.4];
public bool $notArray = false;
protected array $fillable = [
'name',
'email',
];
public array $newMultiArrayProperty = ['arrayProperty' => [0 => 1, 1 => 'string', 2 => true]];
public array $newMultiArrayProperty = [
'arrayProperty' => [0 => 1, 1 => 'string', 2 => true],
'arrayProperty2' => [1, 2, 3],
];

public function __construct()
{
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/PHPFileBuilderTest/original/trait.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
trait SomeTrait {
public float $floatProperty;

protected array $fillable = [
'name',
'email',
];

public function method1()
{
return $this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@
class SomeClass
{
public string $stringProperty = 'some value';
protected array $tags = ['one', 'two', 'three', 4];
protected array $tags = ['one', 'two', 3, true, 5.5, 78.4, 'three', 4];
public bool $notArray = false;
protected array $fillable = [
'name',
'email',
'age',
];
public array $newMultiArrayProperty = ['arrayProperty' => [0 => 1, 1 => 'string', 2 => true], [
'array' => [
0 => 2,
1 => 'string',
2 => false,
public array $newMultiArrayProperty = [
'arrayProperty' => [0 => 1, 1 => 'string', 2 => true],
'arrayProperty2' => [1, 2, 3],
[
'array' => [
0 => 2,
1 => 'string',
2 => false,
],
],
]];
];
public array $role = [
'admin',
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace RonasIT\Larabuilder\Tests\Support;

use Some;

class SomeClass
{
public string $stringProperty = 'some value';
protected array $tags = ['one', 78.4];
public bool $notArray = false;
protected array $fillable = [
'email',
];
public array $newMultiArrayProperty = [
'arrayProperty2' => [1, 2, 3],
];

public function __construct()
{
}

public function someMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace RonasIT\Larabuilder\Tests\Support;

use Some;
use Test;
use Some\SomeTrait;

/**
* Test
*/
class SomeClass implements Test, Some
{
use SomeTrait;

public function __construct()
{
}

public function someMethod()
{
}
}
4 changes: 4 additions & 0 deletions tests/fixtures/PHPFileBuilderTest/results/trait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

trait SomeTrait {
public int $floatProperty = 56;

protected array $fillable = [
'email',
];
public array $tags = [
'three',
4,
Expand Down