Skip to content

Commit

Permalink
implement MissingMagicSerializationMethodsRule
Browse files Browse the repository at this point in the history
  • Loading branch information
clxmstaab authored and ondrejmirtes committed Oct 16, 2022
1 parent c5a696c commit 74912c8
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 0 deletions.
5 changes: 5 additions & 0 deletions build/enum-adapter-errors.neon
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ parameters:
count: 1
path: ../src/Rules/Methods/MissingMethodImplementationRule.php

-
message: "#^Call to method getMethods\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#"
count: 1
path: ../src/Rules/Methods/MissingMagicSerializationMethodsRule.php

-
message: "#^Call to method isFinal\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#"
count: 2
Expand Down
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ parameters:
notAnalysedTrait: true
curlSetOptTypes: true
listType: true
missingMagicSerializationRule: true
5 changes: 5 additions & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ conditionalTags:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
PHPStan\Rules\Api\RuntimeReflectionInstantiationRule:
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule:
phpstan.rules.rule: %featureToggles.missingMagicSerializationRule%

rules:
- PHPStan\Rules\Api\ApiInstantiationRule
Expand Down Expand Up @@ -259,3 +261,6 @@ services:
class: PHPStan\Reflection\ConstructorsHelper
arguments:
additionalConstructors: %additionalConstructors%

-
class: PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule
2 changes: 2 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ parameters:
notAnalysedTrait: false
curlSetOptTypes: false
listType: false
missingMagicSerializationRule: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down Expand Up @@ -259,6 +260,7 @@ parametersSchema:
notAnalysedTrait: bool()
curlSetOptTypes: bool()
listType: bool()
missingMagicSerializationRule: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,9 @@ public function strSplitReturnsEmptyArray(): bool
return $this->versionId >= 80200;
}

public function serializableRequiresMagicMethods(): bool
{
return $this->versionId >= 80100;
}

}
81 changes: 81 additions & 0 deletions src/Rules/Methods/MissingMagicSerializationMethodsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
use PHPStan\Node\InClassNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use Serializable;
use function sprintf;
use function strtolower;

/**
* @implements Rule<InClassNode>
*/
class MissingMagicSerializationMethodsRule implements Rule
{

public function __construct(private PhpVersion $phpversion)
{
}

public function getNodeType(): string
{
return InClassNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
$classReflection = $node->getClassReflection();
if (!$this->phpversion->serializableRequiresMagicMethods()) {
return [];
}
if (!$classReflection->implementsInterface(Serializable::class)) {
return [];
}
if ($classReflection->isAbstract() || $classReflection->isInterface() || $classReflection->isEnum()) {
return [];
}

$messages = [];

try {
$nativeMethods = $classReflection->getNativeReflection()->getMethods();
} catch (IdentifierNotFound) {
return [];
}

$missingMagicSerialize = true;
$missingMagicUnserialize = true;
foreach ($nativeMethods as $method) {
if (strtolower($method->getName()) === '__serialize') {
$missingMagicSerialize = false;
}
if (strtolower($method->getName()) !== '__unserialize') {
continue;
}

$missingMagicUnserialize = false;
}

if ($missingMagicSerialize) {
$messages[] = RuleErrorBuilder::message(sprintf(
'Non-abstract class %s implements the Serializable interface, but does not implement __serialize().',
$classReflection->getDisplayName(),
))->tip('See https://wiki.php.net/rfc/phase_out_serializable')->build();
}
if ($missingMagicUnserialize) {
$messages[] = RuleErrorBuilder::message(sprintf(
'Non-abstract class %s implements the Serializable interface, but does not implement __unserialize().',
$classReflection->getDisplayName(),
))->tip('See https://wiki.php.net/rfc/phase_out_serializable')->build();
}

return $messages;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<MissingMagicSerializationMethodsRule>
*/
class MissingMagicSerializationMethodsRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new MissingMagicSerializationMethodsRule(new PhpVersion(PHP_VERSION_ID));
}

public function testRule(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/data/missing-serialization.php'], [
[
'Non-abstract class MissingMagicSerializationMethods\myObj implements the Serializable interface, but does not implement __serialize().',
14,
'See https://wiki.php.net/rfc/phase_out_serializable',
],
[
'Non-abstract class MissingMagicSerializationMethods\myObj implements the Serializable interface, but does not implement __unserialize().',
14,
'See https://wiki.php.net/rfc/phase_out_serializable',
],
]);
}

}
40 changes: 40 additions & 0 deletions tests/PHPStan/Rules/Methods/data/missing-serialization.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php // lint >= 8.1

namespace MissingMagicSerializationMethods;

use Serializable;

abstract class abstractObj implements Serializable {
public function serialize() {
}
public function unserialize($data) {
}
}

class myObj implements Serializable {
public function serialize() {
}
public function unserialize($data) {
}
}

enum myEnum implements Serializable {
case X;
case Y;

public function serialize() {
}
public function unserialize($data) {
}
}

abstract class allGood implements Serializable {
public function serialize() {
}
public function unserialize($data) {
}
public function __serialize() {
}
public function __unserialize($data) {
}
}

0 comments on commit 74912c8

Please sign in to comment.