Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ new PHPFileBuilder(app_path('Models/User.php'))

### Features

#### setNamespace

Set or replace the namespace declaration in a PHP file. If the file has no namespace, it will be added. If the namespace is already the same, no changes are made.

#### setProperty

Add new class property with the passed value and passed access level in case property does not exist in the class. Otherwise
Expand Down
8 changes: 8 additions & 0 deletions src/Builders/PHPFileBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use RonasIT\Larabuilder\Visitors\PropertyVisitors\AddArrayPropertyItem;
use RonasIT\Larabuilder\Visitors\PropertyVisitors\RemoveArrayPropertyItem;
use RonasIT\Larabuilder\Visitors\PropertyVisitors\SetProperty;
use RonasIT\Larabuilder\Visitors\SetNamespace;

class PHPFileBuilder
{
Expand All @@ -41,6 +42,13 @@ public function __construct(
$this->traverser = new NodeTraverser();
}

public function setNamespace(string $namespace): self
{
$this->traverser->addVisitor(new SetNamespace($namespace));

return $this;
}

public function setProperty(string $name, mixed $value, AccessModifierEnum $accessModifier = AccessModifierEnum::Public): self
{
$this->traverser->addVisitor(new SetProperty($name, $value, $accessModifier));
Expand Down
44 changes: 44 additions & 0 deletions src/Visitors/SetNamespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace RonasIT\Larabuilder\Visitors;

use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\NodeVisitorAbstract;

class SetNamespace extends NodeVisitorAbstract
{
public function __construct(
protected string $namespace,
) {
}

public function afterTraverse(array $nodes): ?array
{
$declares = [];

foreach ($nodes as $key => $node) {
if ($node instanceof Namespace_) {
return $this->updateNamespace($node);
}

if ($node instanceof Declare_) {
$declares[] = $node;

unset($nodes[$key]);
Comment on lines +26 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict declare hoisting to leading strict_types

The new namespace insertion path hoists every top-level declare out of its original position before wrapping the rest of the file in namespace ...;. This changes runtime behavior for valid files that use non-strict_types declares (for example declare(ticks=1);) after some statements, because the directive is now applied from the top of the script. To avoid semantic changes, only preserve leading declare(strict_types=1) (or otherwise reject unsupported declare layouts) instead of relocating all Declare_ nodes.

Useful? React with 👍 / 👎.

}
}

return [...$declares, new Namespace_(new Name($this->namespace), array_values($nodes))];
}

protected function updateNamespace(Namespace_ $node): ?array
{
if ($node->name->toString() !== $this->namespace) {
$node->name = new Name($this->namespace);
}

return null;
}
}
42 changes: 42 additions & 0 deletions tests/PHPFileBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,48 @@ class PHPFileBuilderTest extends TestCase
{
use PHPFileBuilderTestMockTrait;

public function testSetNamespaceOnFileWithoutNamespace(): void
{
$file = $this->generateOriginalStructurePath('class_empty.php');

$this->mockNativeFunction(
'RonasIT\Larabuilder\Builders',
$this->callFilePutContent($file, 'namespace_set.php'),
);

new PHPFileBuilder($file)
->setNamespace('App\\Models')
->save();
}

public function testSetNamespaceReplacesExistingNamespace(): void
{
$file = $this->generateOriginalStructurePath('enum.php');

$this->mockNativeFunction(
'RonasIT\Larabuilder\Builders',
$this->callFilePutContent($file, 'namespace_update.php'),
);

new PHPFileBuilder($file)
->setNamespace('App\\Models')
->save();
}

public function testSetNamespaceDoesNothingWhenSameNamespace(): void
{
$file = $this->generateOriginalStructurePath('class.php');

$this->mockNativeFunction(
'RonasIT\Larabuilder\Builders',
$this->callFilePutContent($file, 'class_unchanged.php'),
);

new PHPFileBuilder($file)
->setNamespace('RonasIT\Larabuilder\Tests\Support')
->save();
}

public function testSetProperty(): void
{
$file = $this->generateOriginalStructurePath('class_with_properties.php');
Expand Down
7 changes: 7 additions & 0 deletions tests/Support/OriginStructures/class_empty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

class SomeClass
{
}
9 changes: 9 additions & 0 deletions tests/fixtures/PHPFileBuilderTest/namespace_set.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace App\Models;

class SomeClass
{
}
14 changes: 14 additions & 0 deletions tests/fixtures/PHPFileBuilderTest/namespace_update.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Models;

enum SomeEnum
{
case First;
case Second;

public static function toArray(): array
{
return self::cases();
}
}