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
2 changes: 2 additions & 0 deletions config/sets/laravel130.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use RectorLaravel\Rector\Class_\QueuePropertyToQueueAttributeRector;
use RectorLaravel\Rector\Class_\TablePropertyToTableAttributeRector;
use RectorLaravel\Rector\Class_\TimeoutPropertyToTimeoutAttributeRector;
use RectorLaravel\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector;
use RectorLaravel\Rector\Class_\TouchesPropertyToTouchesAttributeRector;
use RectorLaravel\Rector\Class_\TriesPropertyToTriesAttributeRector;
use RectorLaravel\Rector\Class_\UniqueForPropertyToUniqueForAttributeRector;
Expand All @@ -36,6 +37,7 @@
$rectorConfig->rule(QueuePropertyToQueueAttributeRector::class);
$rectorConfig->rule(TablePropertyToTableAttributeRector::class);
$rectorConfig->rule(TimeoutPropertyToTimeoutAttributeRector::class);
$rectorConfig->rule(TimestampsPropertyToTimestampsAttributeRector::class);
$rectorConfig->rule(TouchesPropertyToTouchesAttributeRector::class);
$rectorConfig->rule(TriesPropertyToTriesAttributeRector::class);
$rectorConfig->rule(UniqueForPropertyToUniqueForAttributeRector::class);
Expand Down
19 changes: 19 additions & 0 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,25 @@ Changes the timeout property to use the Timeout attribute

<br>

## TimestampsPropertyToTimestampsAttributeRector

Changes the timestamps property to use the WithoutTimestamps attribute

- class: [`RectorLaravel\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector`](../src/Rector/Class_/TimestampsPropertyToTimestampsAttributeRector.php)

```diff
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;

+#[WithoutTimestamps]
final class User extends Model
{
- public $timestamps = false;
}
```

<br>

## TouchesPropertyToTouchesAttributeRector

Changes model touches property to use the touches attribute
Expand Down
127 changes: 127 additions & 0 deletions src/Rector/Class_/TimestampsPropertyToTimestampsAttributeRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ObjectType;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class TimestampsPropertyToTimestampsAttributeRector extends AbstractRector
{
public function __construct(
private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer,
) {}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Replace $timestamps = false property with #[WithoutTimestamps] attribute in Eloquent Models',
[
new CodeSample(
<<<'PHP'
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
public $timestamps = false;
}
PHP,
<<<'PHP'
use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class User extends Model
{
}
PHP,
),
],
);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (!$this->isAnEloquentModelClass($node)) {
return null;
}

if ($this->phpAttributeAnalyzer->hasPhpAttribute($node, 'Illuminate\Database\Eloquent\Attributes\WithoutTimestamps')) {
return null;
}

$timestampsProperty = $node->getProperty('timestamps');
if ($timestampsProperty === null){
return null;
}

$this->removePropertyFromClass($node, $timestampsProperty);
if($this->isFalseProperty($timestampsProperty)){
$this->addWithoutTimestampsAttribute($node);
}

return $node;
}

private function isAnEloquentModelClass(Class_ $class): bool
{
if ($class->extends === null) {
return false;
}

return $this->isName($class->extends, 'Illuminate\Database\Eloquent\Model') || $this->isName($class->extends, 'Model');
}

private function isFalseProperty(Property $timestampsProperty): bool
{
if (count($timestampsProperty->props) === 0) {
return false;
}

$default = $timestampsProperty->props[0]->default;
if ($default === null) {
return false;
}

return $default instanceof ConstFetch && $this->isName($default->name, 'false');
}

private function removePropertyFromClass(Class_ $class, Property $timestampsProperty): void
{
foreach ($class->stmts as $key => $stmt) {
if ($stmt === $timestampsProperty) {
unset($class->stmts[$key]);
break;
}
}
}

private function addWithoutTimestampsAttribute(Class_ $class): void
{
$class->attrGroups[] = new AttributeGroup([
new Attribute(new FullyQualified('Illuminate\Database\Eloquent\Attributes\WithoutTimestamps')),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Model;

class TimestampsFalse extends Model
{
public $timestamps = false;
}

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Model;

#[\Illuminate\Database\Eloquent\Attributes\WithoutTimestamps]
class TimestampsFalse extends Model
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Model;

class TimestampsTrue extends Model
{
public $timestamps = true;
}

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Model;

class TimestampsTrue extends Model
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Model;

#[Illuminate\Database\Eloquent\Attributes\WithoutTimestamps]
class ExistingAttribute extends Model
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class ExistingFqnAttribute extends Model
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace RectorLaravel\Tests\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector\Fixture;

class NotAModel
{
public $timestamps = true;
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace PlayPlay\Rector\Tests\Rule\TimestampsPropertyToTimestampsAttributeRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class TimestampsPropertyToTimestampsAttributeRectorTest extends AbstractRectorTestCase
{
public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

/**
* @test
*/
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use RectorLaravel\Rector\Class_\TimestampsPropertyToTimestampsAttributeRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rules([
TimestampsPropertyToTimestampsAttributeRector::class,
]);
};