44
55namespace Tamedevelopers \Support ;
66
7+ use FilesystemIterator ;
78use RecursiveIteratorIterator ;
89use RecursiveDirectoryIterator ;
910use Tamedevelopers \Support \Capsule \File ;
1011use Tamedevelopers \Support \Traits \ServerTrait ;
1112
12- class AutoloadRegister{
13-
13+
14+ /**
15+ * Improved autoloader and file loader.
16+ *
17+ * Differences from AutoloadRegister:
18+ * - Skips dot entries during directory scan (better perf, fewer edge cases)
19+ * - Supports class, trait, interface, and enum (PHP 8.1+)
20+ * - Idempotent autoload registration (won't re-register repeatedly)
21+ * - Same public API and usage: AutoloadRegister2::load('dir') or ::load(['dir1','dir2'])
22+ */
23+ class AutoloadRegister
24+ {
1425 use ServerTrait;
15-
26+
1627 /**
17- * The base directory to scan for classes and files.
18- * @var string
28+ * Directories to scan
29+ * @var array< string>
1930 */
20- private static $ baseDirectory ;
31+ private static array $ baseDirectories = [] ;
2132
2233 /**
23- * The class map that stores the class names and their corresponding file paths.
24- * @var array
34+ * FQN class => file path
35+ * @var array<string,string>
2536 */
26- private static $ classMap = [];
37+ private static array $ classMap = [];
2738
2839 /**
29- * The file map that stores the file paths and their corresponding relative paths.
30- * @var array
40+ * relative php file (no class/trait/interface/enum) => file path
41+ * @var array<string,string>
3142 */
32- private static $ fileMap = [];
43+ private static array $ fileMap = [];
3344
3445 /**
35- * Autoload function to load class and files in a given folder
46+ * Ensure we only register spl_autoload once.
47+ */
48+ private static bool $ registered = false ;
49+
50+ /**
51+ * Autoload function to load classes and files in the given folder(s).
3652 *
37- * @param string|array $baseDirectory
38- * - The directory path to load
39- * - Do not include the root path, as The Application already have a copy of your path
40- * - e.g [classes] or [app/main]
41- *
42- * @return void
53+ * @param string|array $baseDirectory Directory path(s) relative to application base.
54+ * - Do not include the root path. e.g. 'classes' or 'app/main'
4355 */
44- public static function load (string |array $ baseDirectory )
56+ public static function load (string |array $ baseDirectory ): void
4557 {
46- if (is_array ($ baseDirectory )){
47- foreach ($ baseDirectory as $ directory ){
48- self ::$ baseDirectory = self ::formatWithBaseDirectory ($ directory );
49- // only allow is an existing directory
50- if (is_dir (self ::$ baseDirectory )){
51- self ::boot ();
52- }
53- }
54- } else {
55- self ::$ baseDirectory = self ::formatWithBaseDirectory ($ baseDirectory );
56- // only allow is an existing directory
57- if (is_dir (self ::$ baseDirectory )){
58- self ::boot ();
58+ self ::$ baseDirectories = [];
59+
60+ $ dirs = is_array ($ baseDirectory ) ? $ baseDirectory : [$ baseDirectory ];
61+ foreach ($ dirs as $ directory ) {
62+ $ path = self ::formatWithBaseDirectory ($ directory );
63+ if (File::isDirectory ($ path )) {
64+ self ::$ baseDirectories [] = $ path ;
5965 }
6066 }
67+
68+ if (empty (self ::$ baseDirectories )) {
69+ return ; // nothing to do
70+ }
71+
72+ self ::boot ();
6173 }
6274
6375 /**
64- * Boot the autoloader by setting the base directory,
65- * - Scanning the directory, and registering the autoload method.
66- * @return void
76+ * Boot the autoloader by scanning directories and registering autoload.
6777 */
68- private static function boot ()
78+ private static function boot (): void
6979 {
70- self ::generateClassMap ();
71- self ::generateFileMap ();
80+ // reset maps to avoid duplicates across repeated calls
81+ self ::$ classMap = [];
82+ self ::$ fileMap = [];
83+
84+ foreach (self ::$ baseDirectories as $ base ) {
85+ self ::generateClassMapFor ($ base );
86+ self ::generateFileMapFor ($ base );
87+ }
88+
7289 self ::loadFiles ();
73- spl_autoload_register ([__CLASS__ , 'loadClass ' ]);
90+
91+ if (!self ::$ registered ) {
92+ spl_autoload_register ([__CLASS__ , 'loadClass ' ]);
93+ self ::$ registered = true ;
94+ }
7495 }
7596
7697 /**
77- * Autoload function to load the class file based on the class name.
78- *
79- * @param string $className The name of the class to load.
80- * @return void
98+ * PSR-like autoload callback using the internally built class map.
8199 */
82- private static function loadClass ($ className )
100+ private static function loadClass (string $ className ): void
83101 {
102+ $ className = ltrim ($ className , '\\' );
84103 $ filePath = self ::$ classMap [$ className ] ?? null ;
85- if ($ filePath && file_exists ($ filePath )) {
104+ if ($ filePath && File:: exists ($ filePath )) {
86105 require_once $ filePath ;
87106 }
88107 }
89108
90109 /**
91- * Load the files from the file map.
92- *
93- * @return void
110+ * Load standalone files (without class/trait/interface/enum) from the file map.
94111 */
95- private static function loadFiles ()
112+ private static function loadFiles (): void
96113 {
97- foreach (self ::$ fileMap as $ fileName => $ filePath ) {
98- if (file_exists ($ filePath )) {
114+ foreach (self ::$ fileMap as $ filePath ) {
115+ if (File:: exists ($ filePath )) {
99116 require_once $ filePath ;
100117 }
101118 }
102119 }
103120
104121 /**
105- * Generate the class map by scanning the base directory and its subdirectories.
106- *
107- * @return void
122+ * Build class map for a single base directory.
108123 */
109- private static function generateClassMap ()
124+ private static function generateClassMapFor ( string $ base ): void
110125 {
111- $ fileIterator = new RecursiveIteratorIterator (
112- new RecursiveDirectoryIterator (self :: $ baseDirectory )
126+ $ iterator = new RecursiveIteratorIterator (
127+ new RecursiveDirectoryIterator ($ base , FilesystemIterator:: SKIP_DOTS )
113128 );
114129
115- foreach ($ fileIterator as $ file ) {
130+ foreach ($ iterator as $ file ) {
116131 if ($ file ->isFile () && $ file ->getExtension () === 'php ' ) {
117- $ filePath = $ file ->getPathname ();
118- $ className = self ::getClassName ($ filePath );
119- if (! is_null ( $ className) ) {
132+ $ filePath = $ file ->getPathname ();
133+ $ className = self ::getClassName ($ filePath );
134+ if ($ className !== null ) {
120135 self ::$ classMap [ltrim ($ className , '\\' )] = self ::pathReplacer ($ filePath );
121136 }
122137 }
123138 }
124139 }
125140
126141 /**
127- * Generate the file map by scanning the base directory and its subdirectories.
128- *
129- * @return void
142+ * Build file map (php files without class/trait/interface/enum) for a base directory.
130143 */
131- private static function generateFileMap ()
144+ private static function generateFileMapFor ( string $ base ): void
132145 {
133- $ fileIterator = new RecursiveIteratorIterator (
134- new RecursiveDirectoryIterator (self :: $ baseDirectory )
146+ $ iterator = new RecursiveIteratorIterator (
147+ new RecursiveDirectoryIterator ($ base , FilesystemIterator:: SKIP_DOTS )
135148 );
136149
137- foreach ($ fileIterator as $ file ) {
150+ foreach ($ iterator as $ file ) {
138151 if ($ file ->isFile () && $ file ->getExtension () === 'php ' ) {
139- $ filePath = $ file ->getPathname ();
152+ $ filePath = $ file ->getPathname ();
140153 $ className = self ::getClassName ($ filePath );
141-
142154 if ($ className === null ) {
143- $ relativePath = self ::getRelativePath ($ filePath );
155+ $ relativePath = self ::getRelativePath ($ base , $ filePath );
144156 self ::$ fileMap [$ relativePath ] = self ::pathReplacer ($ filePath );
145157 }
146158 }
147159 }
148160 }
149161
150162 /**
151- * Get the relative path from the file path.
152- *
153- * @param string $filePath The file path.
154- * @return string The relative path.
163+ * Get the relative path for a file with respect to the provided base directory.
155164 */
156- private static function getRelativePath ($ filePath )
165+ private static function getRelativePath (string $ base , string $ filePath ): string
157166 {
158- $ relativePath = substr ($ filePath , strlen (self :: $ baseDirectory ));
167+ $ relativePath = substr ($ filePath , strlen ($ base ));
159168 return ltrim ($ relativePath , '/ \\' );
160169 }
161170
162171 /**
163- * Get the class name from the file path.
164- *
165- * @param string $filePath The file path.
166- * @return string|null The class name, or null if not found.
172+ * Parse a PHP file and return the fully-qualified class/trait/interface/enum name, or null.
167173 */
168- private static function getClassName ($ filePath )
174+ private static function getClassName (string $ filePath ): ? string
169175 {
170- $ namespace = '' ;
171- $ content = File::get ($ filePath );
172- $ tokens = token_get_all ($ content );
173- $ count = count ($ tokens );
176+ $ namespace = '' ;
177+ $ content = File::get ($ filePath );
178+ if ($ content === '' || $ content === null ) {
179+ return null ;
180+ }
181+
182+ $ tokens = token_get_all ($ content );
183+ $ count = count ($ tokens );
184+
185+ // Target tokens to detect named declarations
186+ $ targets = [T_CLASS , T_TRAIT , T_INTERFACE ];
187+ if (defined ('T_ENUM ' )) {
188+ $ targets [] = T_ENUM ; // PHP 8.1+
189+ }
174190
175191 for ($ i = 0 ; $ i < $ count ; $ i ++) {
176- if ($ tokens [$ i ][0 ] === T_NAMESPACE ) {
192+ // Namespace collection
193+ if (is_array ($ tokens [$ i ]) && $ tokens [$ i ][0 ] === T_NAMESPACE ) {
194+ $ namespace = '' ;
177195 for ($ j = $ i + 1 ; $ j < $ count ; $ j ++) {
178- if ($ tokens [$ j ][ 0 ] === T_STRING || $ tokens [$ j ][0 ] === T_NS_SEPARATOR ) {
196+ if (is_array ( $ tokens [$ j ]) && ( $ tokens [ $ j ][ 0 ] === T_STRING || $ tokens [$ j ][0 ] === T_NS_SEPARATOR ) ) {
179197 $ namespace .= $ tokens [$ j ][1 ];
180198 } elseif ($ tokens [$ j ] === '{ ' || $ tokens [$ j ] === '; ' ) {
181199 break ;
182200 }
183201 }
184202 }
185203
186- if ($ tokens [$ i ][0 ] === T_CLASS || $ tokens [$ i ][0 ] === T_TRAIT ) {
204+ // Class/Trait/Interface/Enum name collection
205+ if (is_array ($ tokens [$ i ]) && in_array ($ tokens [$ i ][0 ], $ targets , true )) {
206+ // Skip anonymous class: "class(" pattern (no T_STRING name)
187207 for ($ j = $ i + 1 ; $ j < $ count ; $ j ++) {
188- if ($ tokens [$ j ] === '{ ' || $ tokens [$ j ] === 'extends ' || $ tokens [$ j ] === 'implements ' || $ tokens [$ j ] === 'use ' ) {
208+ if ($ tokens [$ j ] === '{ ' || $ tokens [$ j ] === '( ' ) {
209+ // '{' for regular bodies, '(' likely anonymous class
189210 break ;
190- } elseif ($ tokens [$ j ][0 ] === T_STRING ) {
191- return $ namespace . '\\' . $ tokens [$ j ][1 ];
211+ } elseif (is_array ($ tokens [$ j ]) && $ tokens [$ j ][0 ] === T_STRING ) {
212+ $ name = $ tokens [$ j ][1 ];
213+ return ltrim (($ namespace !== '' ? $ namespace . '\\' : '' ) . $ name , '\\' );
192214 }
193215 }
194216 }
195217 }
196- return ;
218+
219+ return null ;
197220 }
198221
199222}
0 commit comments