2121use Phauthentic \BcCheck \ValueObject \ClassInfo ;
2222use Phauthentic \BcCheck \ValueObject \MethodInfo ;
2323use Phauthentic \BcCheck \ValueObject \ParameterInfo ;
24+ use Phauthentic \BcCheck \ValueObject \TypeInfo ;
2425
2526final readonly class MethodSignatureChangedDetector implements BcBreakDetectorInterface
2627{
@@ -32,14 +33,12 @@ public function detect(ClassInfo $before, ClassInfo $after): array
3233 $ afterMethod = $ after ->getMethod ($ beforeMethod ->name );
3334
3435 if ($ afterMethod === null ) {
35- // Method removed - handled by MethodRemovedDetector
36+ // * Method removed - handled by MethodRemovedDetector
3637 continue ;
3738 }
3839
3940 $ methodBreaks = $ this ->compareSignatures ($ before ->name , $ beforeMethod , $ afterMethod );
40- foreach ($ methodBreaks as $ break ) {
41- $ breaks [] = $ break ;
42- }
41+ $ breaks = [...$ breaks , ...$ methodBreaks ];
4342 }
4443
4544 return $ breaks ;
@@ -52,26 +51,41 @@ private function compareSignatures(string $className, MethodInfo $before, Method
5251 {
5352 $ breaks = [];
5453
55- // Check if new required parameters were added
54+ $ requiredParamBreak = $ this ->checkRequiredParametersAdded ($ className , $ before , $ after );
55+ if ($ requiredParamBreak !== null ) {
56+ $ breaks [] = $ requiredParamBreak ;
57+ }
58+
59+ $ paramBreaks = $ this ->compareExistingParameters ($ className , $ before , $ after );
60+ foreach ($ paramBreaks as $ break ) {
61+ $ breaks [] = $ break ;
62+ }
63+
64+ return $ breaks ;
65+ }
66+
67+ private function checkRequiredParametersAdded (string $ className , MethodInfo $ before , MethodInfo $ after ): ?BcBreak
68+ {
5669 $ beforeRequired = $ before ->getRequiredParameterCount ();
5770 $ afterRequired = $ after ->getRequiredParameterCount ();
5871
5972 if ($ afterRequired > $ beforeRequired ) {
60- $ breaks [] = new BcBreak (
61- message: sprintf (
62- 'Method %s::%s() has more required parameters (%d -> %d) ' ,
63- $ className ,
64- $ before ->name ,
65- $ beforeRequired ,
66- $ afterRequired ,
67- ),
73+ return $ this ->createBreak (
74+ message: $ this ->formatRequiredParametersAddedMessage ($ className , $ before ->name , $ beforeRequired , $ afterRequired ),
6875 className: $ className ,
6976 memberName: $ before ->name ,
70- type: BcBreakType::MethodSignatureChanged,
7177 );
7278 }
7379
74- // Check parameter type changes for existing parameters
80+ return null ;
81+ }
82+
83+ /**
84+ * @return list<BcBreak>
85+ */
86+ private function compareExistingParameters (string $ className , MethodInfo $ before , MethodInfo $ after ): array
87+ {
88+ $ breaks = [];
7589 $ minCount = min (count ($ before ->parameters ), count ($ after ->parameters ));
7690
7791 for ($ i = 0 ; $ i < $ minCount ; $ i ++) {
@@ -87,6 +101,21 @@ className: $className,
87101 return $ breaks ;
88102 }
89103
104+ private function formatRequiredParametersAddedMessage (
105+ string $ className ,
106+ string $ methodName ,
107+ int $ beforeCount ,
108+ int $ afterCount ,
109+ ): string {
110+ return sprintf (
111+ 'Method %s::%s() has more required parameters (%d -> %d) ' ,
112+ $ className ,
113+ $ methodName ,
114+ $ beforeCount ,
115+ $ afterCount ,
116+ );
117+ }
118+
90119 /**
91120 * @return list<BcBreak>
92121 */
@@ -99,67 +128,149 @@ private function compareParameters(
99128 ): array {
100129 $ breaks = [];
101130
102- // Parameter type changed (added or made more restrictive)
131+ $ typeBreak = $ this ->checkParameterTypeChange ($ className , $ methodName , $ before , $ after );
132+ if ($ typeBreak !== null ) {
133+ $ breaks [] = $ typeBreak ;
134+ }
135+
136+ $ defaultBreak = $ this ->checkParameterDefaultRemoved ($ className , $ methodName , $ before , $ after );
137+ if ($ defaultBreak !== null ) {
138+ $ breaks [] = $ defaultBreak ;
139+ }
140+
141+ $ referenceBreak = $ this ->checkParameterByReferenceChange ($ className , $ methodName , $ before , $ after );
142+ if ($ referenceBreak !== null ) {
143+ $ breaks [] = $ referenceBreak ;
144+ }
145+
146+ return $ breaks ;
147+ }
148+
149+ private function checkParameterTypeChange (
150+ string $ className ,
151+ string $ methodName ,
152+ ParameterInfo $ before ,
153+ ParameterInfo $ after ,
154+ ): ?BcBreak {
155+ // Type was added (was untyped, now has type)
103156 if ($ before ->type === null && $ after ->type !== null ) {
104- $ breaks [] = new BcBreak (
105- message: sprintf (
106- 'Parameter $%s of %s::%s() now has type %s ' ,
107- $ before ->name ,
108- $ className ,
109- $ methodName ,
110- $ after ->type ->toString (),
111- ),
157+ return $ this ->createBreak (
158+ message: $ this ->formatTypeAddedMessage ($ className , $ methodName , $ before ->name , $ after ->type ),
112159 className: $ className ,
113160 memberName: $ methodName ,
114- type: BcBreakType::MethodSignatureChanged,
115161 );
116- } elseif ($ before ->type !== null && $ after ->type !== null && !$ before ->type ->equals ($ after ->type )) {
117- $ breaks [] = new BcBreak (
118- message: sprintf (
119- 'Parameter $%s of %s::%s() changed type from %s to %s ' ,
120- $ before ->name ,
121- $ className ,
122- $ methodName ,
123- $ before ->type ->toString (),
124- $ after ->type ->toString (),
125- ),
162+ }
163+
164+ // Type changed (both had types, but different)
165+ if ($ before ->type !== null && $ after ->type !== null && !$ before ->type ->equals ($ after ->type )) {
166+ return $ this ->createBreak (
167+ message: $ this ->formatTypeChangedMessage ($ className , $ methodName , $ before ->name , $ before ->type , $ after ->type ),
126168 className: $ className ,
127169 memberName: $ methodName ,
128- type: BcBreakType::MethodSignatureChanged,
129170 );
130171 }
131172
132- // Default removed (was optional, now required)
173+ return null ;
174+ }
175+
176+ private function checkParameterDefaultRemoved (
177+ string $ className ,
178+ string $ methodName ,
179+ ParameterInfo $ before ,
180+ ParameterInfo $ after ,
181+ ): ?BcBreak {
133182 if ($ before ->hasDefault && !$ after ->hasDefault ) {
134- $ breaks [] = new BcBreak (
135- message: sprintf (
136- 'Parameter $%s of %s::%s() is no longer optional ' ,
137- $ before ->name ,
138- $ className ,
139- $ methodName ,
140- ),
183+ return $ this ->createBreak (
184+ message: $ this ->formatDefaultRemovedMessage ($ className , $ methodName , $ before ->name ),
141185 className: $ className ,
142186 memberName: $ methodName ,
143- type: BcBreakType::MethodSignatureChanged,
144187 );
145188 }
146189
147- // By-reference changed
190+ return null ;
191+ }
192+
193+ private function checkParameterByReferenceChange (
194+ string $ className ,
195+ string $ methodName ,
196+ ParameterInfo $ before ,
197+ ParameterInfo $ after ,
198+ ): ?BcBreak {
148199 if ($ before ->isByReference !== $ after ->isByReference ) {
149- $ breaks [] = new BcBreak (
150- message: sprintf (
151- 'Parameter $%s of %s::%s() %s ' ,
152- $ before ->name ,
153- $ className ,
154- $ methodName ,
155- $ after ->isByReference ? 'is now passed by reference ' : 'is no longer passed by reference ' ,
156- ),
200+ return $ this ->createBreak (
201+ message: $ this ->formatByReferenceChangedMessage ($ className , $ methodName , $ before ->name , $ after ->isByReference ),
157202 className: $ className ,
158203 memberName: $ methodName ,
159- type: BcBreakType::MethodSignatureChanged,
160204 );
161205 }
162206
163- return $ breaks ;
207+ return null ;
208+ }
209+
210+ private function createBreak (string $ message , string $ className , string $ memberName ): BcBreak
211+ {
212+ return new BcBreak (
213+ message: $ message ,
214+ className: $ className ,
215+ memberName: $ memberName ,
216+ type: BcBreakType::MethodSignatureChanged,
217+ );
218+ }
219+
220+ private function formatTypeAddedMessage (string $ className , string $ methodName , string $ paramName , TypeInfo $ type ): string
221+ {
222+ return sprintf (
223+ 'Parameter $%s of %s::%s() now has type %s ' ,
224+ $ paramName ,
225+ $ className ,
226+ $ methodName ,
227+ $ type ->toString (),
228+ );
229+ }
230+
231+ private function formatTypeChangedMessage (
232+ string $ className ,
233+ string $ methodName ,
234+ string $ paramName ,
235+ TypeInfo $ beforeType ,
236+ TypeInfo $ afterType ,
237+ ): string {
238+ return sprintf (
239+ 'Parameter $%s of %s::%s() changed type from %s to %s ' ,
240+ $ paramName ,
241+ $ className ,
242+ $ methodName ,
243+ $ beforeType ->toString (),
244+ $ afterType ->toString (),
245+ );
246+ }
247+
248+ private function formatDefaultRemovedMessage (string $ className , string $ methodName , string $ paramName ): string
249+ {
250+ return sprintf (
251+ 'Parameter $%s of %s::%s() is no longer optional ' ,
252+ $ paramName ,
253+ $ className ,
254+ $ methodName ,
255+ );
256+ }
257+
258+ private function formatByReferenceChangedMessage (
259+ string $ className ,
260+ string $ methodName ,
261+ string $ paramName ,
262+ bool $ isNowByReference ,
263+ ): string {
264+ $ changeDescription = $ isNowByReference
265+ ? 'is now passed by reference '
266+ : 'is no longer passed by reference ' ;
267+
268+ return sprintf (
269+ 'Parameter $%s of %s::%s() %s ' ,
270+ $ paramName ,
271+ $ className ,
272+ $ methodName ,
273+ $ changeDescription ,
274+ );
164275 }
165276}
0 commit comments