1+ <?php
2+
3+ namespace lucatume \Rector ;
4+
5+ use PhpParser \Node ;
6+ use PhpParser \Node \Expr \BinaryOp \Identical ;
7+ use PhpParser \Node \Expr \BinaryOp \NotIdentical ;
8+ use PhpParser \Node \Expr \ConstFetch ;
9+ use PhpParser \Node \Expr \FuncCall ;
10+ use PhpParser \Node \Name ;
11+ use PhpParser \Node \Scalar \String_ ;
12+ use Rector \Rector \AbstractRector ;
13+ use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
14+ use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
15+
16+ /**
17+ * Rector rule to downgrade PHP_OS_FAMILY (PHP 7.2+) to PHP_OS (PHP 7.1).
18+ *
19+ * This rule:
20+ * 1. Replaces `PHP_OS_FAMILY === 'Windows'` with `strtolower(substr(PHP_OS, 0, 3)) === 'win'`
21+ * 2. Replaces `PHP_OS_FAMILY !== 'Windows'` with `strtolower(substr(PHP_OS, 0, 3)) !== 'win'`
22+ * 3. Replaces other uses of `PHP_OS_FAMILY` with `PHP_OS`
23+ */
24+ class DowngradePhpOsFamily extends AbstractRector
25+ {
26+ public function getRuleDefinition (): RuleDefinition
27+ {
28+ return new RuleDefinition (
29+ 'Downgrade PHP_OS_FAMILY to PHP_OS for PHP 7.1 compatibility ' ,
30+ [
31+ new CodeSample (
32+ 'if (PHP_OS_FAMILY === \'Windows \') {} ' ,
33+ 'if (strtolower(substr(PHP_OS, 0, 3)) === \'win \') {} '
34+ ),
35+ new CodeSample (
36+ 'if (PHP_OS_FAMILY !== \'Windows \') {} ' ,
37+ 'if (strtolower(substr(PHP_OS, 0, 3)) !== \'win \') {} '
38+ ),
39+ new CodeSample (
40+ 'substr(PHP_OS_FAMILY, 0, 3) ' ,
41+ 'substr(PHP_OS, 0, 3) '
42+ )
43+ ]
44+ );
45+ }
46+
47+ public function getNodeTypes (): array
48+ {
49+ return [Identical::class, NotIdentical::class, ConstFetch::class];
50+ }
51+
52+ /**
53+ * @param Identical|NotIdentical|ConstFetch $node
54+ */
55+ public function refactor (Node $ node ): ?Node
56+ {
57+ if ($ node instanceof Identical || $ node instanceof NotIdentical) {
58+ return $ this ->refactorComparison ($ node );
59+ }
60+
61+ if ($ node instanceof ConstFetch) {
62+ return $ this ->refactorConstFetch ($ node );
63+ }
64+
65+ return null ;
66+ }
67+
68+ /**
69+ * Refactor comparison operations (=== or !==) involving PHP_OS_FAMILY and 'Windows'
70+ */
71+ private function refactorComparison (Identical |NotIdentical $ node ): ?Node
72+ {
73+ // Check if one side is PHP_OS_FAMILY constant and the other is 'Windows'
74+ $ isPhpOsFamily = false ;
75+ $ isWindowsString = false ;
76+
77+ if ($ node ->left instanceof ConstFetch && $ this ->isName ($ node ->left , 'PHP_OS_FAMILY ' )) {
78+ $ isPhpOsFamily = true ;
79+ if ($ node ->right instanceof String_ && $ node ->right ->value === 'Windows ' ) {
80+ $ isWindowsString = true ;
81+ }
82+ } elseif ($ node ->right instanceof ConstFetch && $ this ->isName ($ node ->right , 'PHP_OS_FAMILY ' )) {
83+ $ isPhpOsFamily = true ;
84+ if ($ node ->left instanceof String_ && $ node ->left ->value === 'Windows ' ) {
85+ $ isWindowsString = true ;
86+ }
87+ }
88+
89+ if (!$ isPhpOsFamily || !$ isWindowsString ) {
90+ return null ;
91+ }
92+
93+ // Build: strtolower(substr(PHP_OS, 0, 3))
94+ $ phpOsConst = new ConstFetch (new Name ('PHP_OS ' ));
95+ $ substrCall = new FuncCall (
96+ new Name ('substr ' ),
97+ [
98+ new Node \Arg ($ phpOsConst ),
99+ new Node \Arg (new Node \Scalar \LNumber (0 )),
100+ new Node \Arg (new Node \Scalar \LNumber (3 ))
101+ ]
102+ );
103+ $ strtolowerCall = new FuncCall (
104+ new Name ('strtolower ' ),
105+ [new Node \Arg ($ substrCall )]
106+ );
107+
108+ // Create the 'win' string
109+ $ winString = new String_ ('win ' );
110+
111+ // Create the comparison
112+ if ($ node instanceof Identical) {
113+ return new Identical ($ strtolowerCall , $ winString );
114+ } else {
115+ return new NotIdentical ($ strtolowerCall , $ winString );
116+ }
117+ }
118+
119+ /**
120+ * Refactor direct PHP_OS_FAMILY constant fetch to PHP_OS
121+ */
122+ private function refactorConstFetch (ConstFetch $ node ): ?Node
123+ {
124+ if (!$ this ->isName ($ node , 'PHP_OS_FAMILY ' )) {
125+ return null ;
126+ }
127+
128+ // Don't replace if this is part of a comparison we already handled
129+ $ parent = $ node ->getAttribute ('parent ' );
130+ if ($ parent instanceof Identical || $ parent instanceof NotIdentical) {
131+ // Check if the comparison is with 'Windows'
132+ if ($ parent ->left instanceof String_ && $ parent ->left ->value === 'Windows ' ) {
133+ return null ; // Already handled by refactorComparison
134+ }
135+ if ($ parent ->right instanceof String_ && $ parent ->right ->value === 'Windows ' ) {
136+ return null ; // Already handled by refactorComparison
137+ }
138+ }
139+
140+ // Replace PHP_OS_FAMILY with PHP_OS
141+ return new ConstFetch (new Name ('PHP_OS ' ));
142+ }
143+ }
0 commit comments