File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -16,3 +16,4 @@ parameters:
1616 reportNestedTooWideType : false # tmp
1717 assignToByRefForeachExpr : true
1818 curlSetOptArrayTypes : true
19+ checkDateIntervalConstructor : true
Original file line number Diff line number Diff line change @@ -10,6 +10,8 @@ conditionalTags:
1010 phpstan.rules.rule : %featureToggles.checkParameterCastableToNumberFunctions%
1111 PHPStan\Rules\Functions\PrintfParameterTypeRule :
1212 phpstan.rules.rule : %featureToggles.checkPrintfParameterTypes%
13+ PHPStan\Rules\DateIntervalInstantiationRule :
14+ phpstan.rules.rule : %featureToggles.checkDateIntervalConstructor%
1315
1416autowiredAttributeServices :
1517 # registers rules with #[RegisteredRule] attribute
@@ -22,3 +24,5 @@ services:
2224 class : PHPStan\Rules\Functions\PrintfParameterTypeRule
2325 arguments :
2426 checkStrictPrintfPlaceholderTypes : %checkStrictPrintfPlaceholderTypes%
27+ -
28+ class : PHPStan\Rules\DateIntervalInstantiationRule
Original file line number Diff line number Diff line change @@ -43,6 +43,7 @@ parameters:
4343 reportNestedTooWideType : false
4444 assignToByRefForeachExpr : false
4545 curlSetOptArrayTypes : false
46+ checkDateIntervalConstructor : false
4647 fileExtensions :
4748 - php
4849 checkAdvancedIsset : false
Original file line number Diff line number Diff line change @@ -45,6 +45,7 @@ parametersSchema:
4545 reportNestedTooWideType : bool ()
4646 assignToByRefForeachExpr : bool ()
4747 curlSetOptArrayTypes : bool ()
48+ checkDateIntervalConstructor : bool ()
4849 ])
4950 fileExtensions : listOf (string ())
5051 checkAdvancedIsset : bool ()
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules ;
4+
5+ use DateInterval ;
6+ use PhpParser \Node ;
7+ use PhpParser \Node \Expr \New_ ;
8+ use PHPStan \Analyser \Scope ;
9+ use Throwable ;
10+ use function count ;
11+ use function sprintf ;
12+ use function strtolower ;
13+
14+ /**
15+ * @implements Rule<Node\Expr\New_>
16+ */
17+ final class DateIntervalInstantiationRule implements Rule
18+ {
19+
20+ public function getNodeType (): string
21+ {
22+ return New_::class;
23+ }
24+
25+ /**
26+ * @param New_ $node
27+ */
28+ public function processNode (Node $ node , Scope $ scope ): array
29+ {
30+ if (!$ node ->class instanceof Node \Name) {
31+ return [];
32+ }
33+
34+ if (
35+ count ($ node ->getArgs ()) === 0
36+ || strtolower ((string ) $ node ->class ) !== 'dateinterval '
37+ ) {
38+ return [];
39+ }
40+
41+ $ arg = $ scope ->getType ($ node ->getArgs ()[0 ]->value );
42+ $ errors = [];
43+
44+ foreach ($ arg ->getConstantStrings () as $ constantString ) {
45+ $ dateIntervalString = $ constantString ->getValue ();
46+ try {
47+ new DateInterval ($ dateIntervalString );
48+ } catch (Throwable $ e ) {
49+ $ errors [] = RuleErrorBuilder::message (sprintf (
50+ 'Instantiating DateInterval with %s produces an error: %s ' ,
51+ $ dateIntervalString ,
52+ $ e ->getMessage (),
53+ ))->identifier ('new.dateInterval ' )->build ();
54+ }
55+ }
56+
57+ return $ errors ;
58+ }
59+
60+ }
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules ;
4+
5+ use PHPStan \Testing \RuleTestCase ;
6+ use const PHP_VERSION_ID ;
7+
8+ /**
9+ * @extends RuleTestCase<DateIntervalInstantiationRule>
10+ */
11+ class DateIntervalInstantiationRuleTest extends RuleTestCase
12+ {
13+
14+ protected function getRule (): Rule
15+ {
16+ return new DateIntervalInstantiationRule ();
17+ }
18+
19+ public function test (): void
20+ {
21+ if (PHP_VERSION_ID < 80100 ) {
22+ $ prefix = 'DateInterval::__construct(): ' ;
23+ } else {
24+ $ prefix = '' ;
25+ }
26+
27+ $ this ->analyse (
28+ [__DIR__ . '/data/date-interval-instantiation.php ' ],
29+ [
30+ [
31+ 'Instantiating DateInterval with 1M produces an error: ' . $ prefix . 'Unknown or bad format (1M) ' ,
32+ 5 ,
33+ ],
34+ [
35+ 'Instantiating DateInterval with asdfasdf produces an error: ' . $ prefix . 'Unknown or bad format (asdfasdf) ' ,
36+ 18 ,
37+ ],
38+ [
39+ 'Instantiating DateInterval with produces an error: ' . $ prefix . 'Unknown or bad format () ' ,
40+ 21 ,
41+ ],
42+ [
43+ 'Instantiating DateInterval with 1M produces an error: ' . $ prefix . 'Unknown or bad format (1M) ' ,
44+ 30 ,
45+ ],
46+ [
47+ 'Instantiating DateInterval with invalid produces an error: ' . $ prefix . 'Unknown or bad format (invalid) ' ,
48+ 37 ,
49+ ],
50+ ],
51+ );
52+ }
53+
54+ }
Original file line number Diff line number Diff line change 1+ <?php
2+ namespace DateIntervalInstantiation ;
3+
4+ // invalid - missing P prefix
5+ new \DateInterval ('1M ' );
6+
7+ // valid durations
8+ new \DateInterval ('P1Y ' );
9+ new \DateInterval ('P1M ' );
10+ new \DateInterval ('P1D ' );
11+ new \DateInterval ('PT1H ' );
12+ new \DateInterval ('PT1M ' );
13+ new \DateInterval ('PT1S ' );
14+ new \DateInterval ('P1Y2M3DT4H5M6S ' );
15+ new \DateInterval ('P7D ' );
16+
17+ // invalid
18+ new \DateInterval ('asdfasdf ' );
19+
20+ // empty string is invalid
21+ new \DateInterval ('' );
22+
23+ // non-constant string - should not report
24+ function foo (string $ duration ): void {
25+ new \DateInterval ($ duration );
26+ }
27+
28+ // constant string via variable
29+ $ test = '1M ' ;
30+ new \DateInterval ($ test );
31+
32+ /**
33+ * @param 'invalid' $duration2
34+ */
35+ function bar (string $ duration , string $ duration2 ): void {
36+ new \DateInterval ($ duration );
37+ new \DateInterval ($ duration2 );
38+ }
You can’t perform that action at this time.
0 commit comments