@@ -49,13 +49,7 @@ final class DevToolsPathResolver
4949 */
5050 public static function getPackagePath (string $ path = '' ): string
5151 {
52- $ packageDirectory = \dirname (__DIR__ , 2 );
53-
54- if ('' !== $ path && Path::isAbsolute ($ path )) {
55- throw new InvalidArgumentException ('The DevTools package path MUST be relative to the package root. ' );
56- }
57-
58- return Path::join ($ packageDirectory , $ path );
52+ return self ::resolvePackageRelativePath ($ path );
5953 }
6054
6155 /**
@@ -104,19 +98,10 @@ public static function getPackagePathRelativeToProject(
10498 string $ projectPath = '' ,
10599 string $ packagePath = '' ,
106100 ): string {
107- if (Path::isAbsolute ($ path )) {
108- throw new InvalidArgumentException ('The DevTools package path MUST be relative to the package root. ' );
109- }
110-
111- $ projectPath = Path::canonicalize (WorkingProjectPathResolver::getProjectPath ($ projectPath ));
112- $ packagePath = Path::canonicalize ('' === $ packagePath ? self ::getPackagePath () : $ packagePath );
113- $ packageFilePath = Path::canonicalize (Path::join ($ packagePath , $ path ));
114-
115- try {
116- return Path::makeRelative ($ packageFilePath , $ projectPath );
117- } catch (InvalidArgumentException ) {
118- return $ packageFilePath ;
119- }
101+ return self ::relativizePathFromProject (
102+ self ::resolvePackageRelativePath ($ path , $ packagePath ),
103+ self ::resolveProjectPath ($ projectPath ),
104+ );
120105 }
121106
122107 /**
@@ -130,13 +115,7 @@ public static function getPackagePathRelativeToProject(
130115 */
131116 public static function getRuntimeAutoloadPath (string $ packagePath = '' ): string
132117 {
133- $ packagePath = Path::canonicalize ('' === $ packagePath ? self ::getPackagePath () : $ packagePath );
134-
135- if (self ::isInstalledAsDependency ($ packagePath )) {
136- return Path::canonicalize (Path::join ($ packagePath , '.. ' , '.. ' , 'autoload.php ' ));
137- }
138-
139- return Path::join ($ packagePath , 'vendor ' , 'autoload.php ' );
118+ return Path::join (self ::getRuntimeVendorRoot ($ packagePath ), 'autoload.php ' );
140119 }
141120
142121 /**
@@ -150,13 +129,7 @@ public static function getRuntimeAutoloadPath(string $packagePath = ''): string
150129 */
151130 public static function getRuntimeToolBinaryPath (string $ binary , string $ packagePath = '' ): string
152131 {
153- $ packagePath = Path::canonicalize ('' === $ packagePath ? self ::getPackagePath () : $ packagePath );
154-
155- if (self ::isInstalledAsDependency ($ packagePath )) {
156- return Path::canonicalize (Path::join ($ packagePath , '.. ' , '.. ' , 'bin ' , $ binary ));
157- }
158-
159- return Path::join ($ packagePath , 'vendor ' , 'bin ' , $ binary );
132+ return self ::getRuntimeVendorPath (Path::join ('bin ' , $ binary ), $ packagePath );
160133 }
161134
162135 /**
@@ -170,14 +143,7 @@ public static function getRuntimeToolBinaryPath(string $binary, string $packageP
170143 */
171144 public static function getRuntimeVendorPath (string $ path , string $ packagePath = '' ): string
172145 {
173- $ packagePath = Path::canonicalize ('' === $ packagePath ? self ::getPackagePath () : $ packagePath );
174- $ vendorPath = self ::normalizeVendorRelativePath ($ path );
175-
176- if (self ::isInstalledAsDependency ($ packagePath )) {
177- return Path::canonicalize (Path::join ($ packagePath , '.. ' , '.. ' , $ vendorPath ));
178- }
179-
180- return Path::join ($ packagePath , 'vendor ' , $ vendorPath );
146+ return Path::join (self ::getRuntimeVendorRoot ($ packagePath ), self ::normalizeVendorRelativePath ($ path ));
181147 }
182148
183149 /**
@@ -196,14 +162,10 @@ public static function getPreferredToolBinaryPath(
196162 string $ projectPath = '' ,
197163 string $ packagePath = '' ,
198164 ): string {
199- $ projectPath = '' === $ projectPath ? WorkingProjectPathResolver::getProjectPath () : $ projectPath ;
200- $ projectBinaryPath = Path::join ($ projectPath , 'vendor ' , 'bin ' , $ binary );
201-
202- if (file_exists ($ projectBinaryPath )) {
203- return $ projectBinaryPath ;
204- }
205-
206- return self ::getRuntimeToolBinaryPath ($ binary , $ packagePath );
165+ return self ::preferExistingPath (
166+ self ::getProjectVendorPath (Path::join ('bin ' , $ binary ), $ projectPath ),
167+ self ::getRuntimeToolBinaryPath ($ binary , $ packagePath ),
168+ );
207169 }
208170
209171 /**
@@ -222,15 +184,10 @@ public static function getPreferredVendorPath(
222184 string $ projectPath = '' ,
223185 string $ packagePath = '' ,
224186 ): string {
225- $ projectPath = '' === $ projectPath ? WorkingProjectPathResolver::getProjectPath () : $ projectPath ;
226- $ vendorPath = self ::normalizeVendorRelativePath ($ path );
227- $ projectVendorPath = Path::join ($ projectPath , 'vendor ' , $ vendorPath );
228-
229- if (file_exists ($ projectVendorPath )) {
230- return $ projectVendorPath ;
231- }
232-
233- return self ::getRuntimeVendorPath ($ vendorPath , $ packagePath );
187+ return self ::preferExistingPath (
188+ self ::getProjectVendorPath ($ path , $ projectPath ),
189+ self ::getRuntimeVendorPath ($ path , $ packagePath ),
190+ );
234191 }
235192
236193 /**
@@ -240,9 +197,7 @@ public static function getPreferredVendorPath(
240197 */
241198 public static function isInstalledAsDependency (string $ packagePath = '' ): bool
242199 {
243- $ packagePath = Path::canonicalize ('' === $ packagePath ? self ::getPackagePath () : $ packagePath );
244-
245- return str_contains ($ packagePath , self ::VENDOR_PACKAGE_PATH );
200+ return str_contains (self ::resolvePackageRoot ($ packagePath ), self ::VENDOR_PACKAGE_PATH );
246201 }
247202
248203 /**
@@ -270,4 +225,109 @@ private static function normalizeVendorRelativePath(string $path): string
270225
271226 return $ path ;
272227 }
228+
229+ /**
230+ * Ensures packaged paths stay relative to the DevTools package root.
231+ *
232+ * @param string $path the package-relative path to validate
233+ */
234+ private static function assertRelativePackagePath (string $ path ): void
235+ {
236+ if ('' !== $ path && Path::isAbsolute ($ path )) {
237+ throw new InvalidArgumentException ('The DevTools package path MUST be relative to the package root. ' );
238+ }
239+ }
240+
241+ /**
242+ * Returns a canonical path under the DevTools package root.
243+ *
244+ * @param string $path the package-relative path to resolve
245+ * @param string $packagePath an optional package root path; defaults to the current package root
246+ */
247+ private static function resolvePackageRelativePath (string $ path = '' , string $ packagePath = '' ): string
248+ {
249+ self ::assertRelativePackagePath ($ path );
250+
251+ return Path::canonicalize (Path::join (self ::resolvePackageRoot ($ packagePath ), $ path ));
252+ }
253+
254+ /**
255+ * Returns the canonical DevTools package root.
256+ *
257+ * @param string $packagePath an optional package root path; defaults to the current package root
258+ */
259+ private static function resolvePackageRoot (string $ packagePath = '' ): string
260+ {
261+ return Path::canonicalize ('' === $ packagePath ? \dirname (__DIR__ , 2 ) : $ packagePath );
262+ }
263+
264+ /**
265+ * Returns the canonical working project root.
266+ *
267+ * @param string $projectPath an optional project root path; defaults to the working project root
268+ */
269+ private static function resolveProjectPath (string $ projectPath = '' ): string
270+ {
271+ return Path::canonicalize (WorkingProjectPathResolver::getProjectPath ($ projectPath ));
272+ }
273+
274+ /**
275+ * Returns the active Composer vendor root for the current DevTools installation mode.
276+ *
277+ * @param string $packagePath an optional package root path; defaults to the current package root
278+ */
279+ private static function getRuntimeVendorRoot (string $ packagePath = '' ): string
280+ {
281+ $ packagePath = self ::resolvePackageRoot ($ packagePath );
282+
283+ if (self ::isInstalledAsDependency ($ packagePath )) {
284+ return Path::canonicalize (Path::join ($ packagePath , '.. ' , '.. ' ));
285+ }
286+
287+ return Path::join ($ packagePath , 'vendor ' );
288+ }
289+
290+ /**
291+ * Returns a vendor path under the active project root.
292+ *
293+ * @param string $path the vendor-relative path to resolve
294+ * @param string $projectPath an optional project root path; defaults to the working project root
295+ */
296+ private static function getProjectVendorPath (string $ path , string $ projectPath = '' ): string
297+ {
298+ return Path::join (self ::resolveProjectPath ($ projectPath ), 'vendor ' , self ::normalizeVendorRelativePath ($ path ));
299+ }
300+
301+ /**
302+ * Returns the preferred path when a project-local candidate exists.
303+ *
304+ * @param string $preferredPath the project-local candidate path
305+ * @param string $fallbackPath the runtime fallback path
306+ */
307+ private static function preferExistingPath (string $ preferredPath , string $ fallbackPath ): string
308+ {
309+ if (file_exists ($ preferredPath )) {
310+ return $ preferredPath ;
311+ }
312+
313+ return $ fallbackPath ;
314+ }
315+
316+ /**
317+ * Returns a path relative to the project root when possible.
318+ *
319+ * When paths do not share the same filesystem root, the original absolute
320+ * path MUST be returned unchanged so callers still receive a usable path.
321+ *
322+ * @param string $path the absolute path to relativize
323+ * @param string $projectPath the absolute project root used as base path
324+ */
325+ private static function relativizePathFromProject (string $ path , string $ projectPath ): string
326+ {
327+ try {
328+ return Path::makeRelative ($ path , $ projectPath );
329+ } catch (InvalidArgumentException ) {
330+ return $ path ;
331+ }
332+ }
273333}
0 commit comments