File Coverage

blib/lib/Class.pm
Criterion Covered Total %
statement 125 127 98.4
branch 29 36 80.5
condition 8 12 66.6
subroutine 20 20 100.0
pod 2 2 100.0
total 184 197 93.4


line stmt bran cond sub pod time code
1             package Class;
2              
3 15     15   784279 use strict;
  15         24  
  15         483  
4 15     15   91 use warnings;
  15         34  
  15         642  
5 15     15   3705 use version;
  15         17900  
  15         76  
6 15     15   1050 use Exporter;
  15         33  
  15         740  
7 15     15   633 use mro ();
  15         928  
  15         3746  
8              
9             our $VERSION = qv('v0.1.1');
10             our $AUTHORITY = 'cpan:MANWAR';
11              
12             our @EXPORT = qw(extends with);
13             our @ISA = qw(Exporter);
14              
15             my %BUILD_METHODS_CACHE;
16             my %METHOD_COPY_CACHE;
17              
18             # Precomputed skip patterns for faster method filtering
19             my %SKIP_METHODS = map { $_ => 1 } qw(
20             BUILD new extends with does import AUTOLOAD DESTROY BEGIN END
21             ISA VERSION EXPORT AUTHORITY INC
22             );
23              
24             sub new {
25 34     34 1 7971 my $class = shift;
26 34         72 my %attrs = @_;
27 34         141 my $self = bless { %attrs }, $class;
28              
29             # Use cached BUILD methods for maximum performance
30 34   66     178 my $build_methods = $BUILD_METHODS_CACHE{$class} ||= _compute_build_methods($class);
31 34         89 $_->($self, \%attrs) for @$build_methods;
32              
33 34         231 return $self;
34             }
35              
36             sub _compute_build_methods {
37 31     31   41 my $class = shift;
38              
39 31         57 my @build_order;
40             my %visited;
41              
42             # Depth-first traversal for true parent-first order
43 31         109 _depth_first_traverse($class, \@build_order, \%visited);
44              
45 31         59 my @build_methods;
46 31         45 foreach my $c (@build_order) {
47 15     15   86 no strict 'refs';
  15         46  
  15         2241  
48 71 100       98 if (defined &{"${c}::BUILD"}) {
  71         236  
49 21         25 push @build_methods, \&{"${c}::BUILD"};
  21         52  
50             }
51             }
52              
53 31         124 return \@build_methods;
54             }
55              
56             sub _depth_first_traverse {
57 73     73   153 my ($class, $order, $visited) = @_;
58              
59 73 100       235 return if $visited->{$class}++;
60              
61 15     15   86 no strict 'refs';
  15         46  
  15         2249  
62 71         89 my @parents = @{"${class}::ISA"};
  71         329  
63              
64             # Process all parents first (depth-first)
65 71         112 foreach my $parent (@parents) {
66 42         170 _depth_first_traverse($parent, $order, $visited);
67             }
68              
69             # Then add current class
70 71         250 push @$order, $class;
71             }
72              
73             sub extends {
74 31     31 1 1268258 my ($maybe_class, @maybe_parents) = @_;
75 31         88 my $child_class = caller;
76              
77 31         130 _delete_build_cache($child_class);
78              
79 31 100       123 my @parents = @maybe_parents ? ($maybe_class, @maybe_parents) : ($maybe_class);
80              
81 15     15   97 no strict 'refs';
  15         34  
  15         4065  
82              
83 31         77 for my $parent_class (@parents) {
84 34 100       126 die "Recursive inheritance detected: $child_class cannot extend itself"
85             if $child_class eq $parent_class;
86              
87             # Efficient parent loading - only load from disk if necessary
88 33 100 100     187 unless ($INC{"$parent_class.pm"} || defined &{"${parent_class}::new"}) {
  30         167  
89 2         5 (my $parent_file = "$parent_class.pm") =~ s{::}{/}g;
90 2         3 eval { require $parent_file };
  2         508  
91             # ignore errors - parent might be defined inline
92             }
93              
94             # Link inheritance if not already linked
95 33 50       45 unless (grep { $_ eq $parent_class } @{"${child_class}::ISA"}) {
  7         26  
  33         211  
96 33         46 push @{"${child_class}::ISA"}, $parent_class;
  33         372  
97             }
98              
99             # Copy public methods from parent to child for direct access
100 33         96 _copy_public_methods($child_class, $parent_class);
101             }
102             }
103              
104             sub _copy_public_methods {
105 33     33   62 my ($child, $parent) = @_;
106              
107             # Use cache to avoid re-copying methods for same parent-child pair
108 33         57 my $cache_key = "$child|$parent";
109 33 50       96 return if $METHOD_COPY_CACHE{$cache_key};
110 33         72 $METHOD_COPY_CACHE{$cache_key} = 1;
111              
112 15     15   124 no strict 'refs';
  15         22  
  15         5262  
113 33         40 my $parent_symtab = \%{"${parent}::"};
  33         76  
114              
115             # Single pass with optimized checks
116 33         228 for my $method (keys %$parent_symtab) {
117             # Skip special methods and private methods quickly
118 741 100       1089 next if $SKIP_METHODS{$method};
119 554 100       656 next if $method =~ /^_/;
120 553 100       692 next if $method =~ /::$/; # Skip nested packages
121              
122             # Skip if already defined in child or not a CODE ref in parent
123 506 100       445 next if defined &{"${child}::${method}"};
  506         1245  
124 505 100       429 next unless defined &{"${parent}::${method}"};
  505         956  
125              
126             # Copy the method
127 485         457 *{"${child}::${method}"} = \&{"${parent}::${method}"};
  485         919  
  485         600  
128             }
129             }
130              
131             sub _delete_build_cache {
132 31     31   60 my ($class) = @_;
133 31         134 delete $BUILD_METHODS_CACHE{$class};
134              
135             # Clear cache for all classes that inherit from this one
136 31         83 for my $cached_class (keys %BUILD_METHODS_CACHE) {
137 42 50       75 if (_inherits_from($cached_class, $class)) {
138 0         0 delete $BUILD_METHODS_CACHE{$cached_class};
139             }
140             }
141              
142             # Also clear method copy cache for affected classes
143 31         106 for my $cache_key (keys %METHOD_COPY_CACHE) {
144 72         171 my ($child, $parent) = split(/\|/, $cache_key);
145 72 100 66     206 if ($child eq $class || _inherits_from($child, $class)) {
146 3         8 delete $METHOD_COPY_CACHE{$cache_key};
147             }
148             }
149             }
150              
151             sub _inherits_from {
152 282     282   384 my ($class, $parent) = @_;
153              
154 15     15   85 no strict 'refs';
  15         20  
  15         2726  
155 282         287 my @isa = @{"${class}::ISA"};
  282         775  
156              
157 282 50       403 return 1 if grep { $_ eq $parent } @isa;
  171         354  
158              
159 282         403 foreach my $direct_parent (@isa) {
160 171 50       274 return 1 if _inherits_from($direct_parent, $parent);
161             }
162              
163 282         644 return 0;
164             }
165              
166             sub import {
167 82     82   148529 my ($class, @args) = @_;
168 82         153 my $caller = caller;
169              
170             # Enable strict and warnings
171 82         504 strict->import;
172 82         1404 warnings->import;
173              
174             # Load Role.pm if exists
175 82         127 eval { require Role };
  82         5412  
176 82 50       190 if (!$@) {
177 15     15   111 no strict 'refs';
  15         24  
  15         1127  
178 82         113 *{"${caller}::with"} = \&Role::with;
  82         346  
179 82         104 *{"${caller}::does"} = \&Role::does;
  82         181  
180             }
181              
182             # Install new and extends
183 15     15   70 no strict 'refs';
  15         31  
  15         2877  
184 82         119 *{"${caller}::new"} = \&Class::new;
  82         226  
185 82         121 *{"${caller}::extends"} = \&Class::extends;
  82         221  
186              
187             # optional extends => Parent
188 82 50 33     20239 if (@args && $args[0] eq 'extends') {
189 0           $class->extends(@args[1..$#args]);
190             }
191             }
192              
193             =head1 NAME
194              
195             Class - Lightweight Perl object system with parent-first BUILD and method copying
196              
197             =head1 VERSION
198              
199             Version v0.1.1
200              
201             =head1 SYNOPSIS
202              
203             use Class;
204              
205             # Simple class with attributes and BUILD
206             package Person;
207             use Class;
208              
209             sub BUILD {
210             my ($self, $attrs) = @_;
211             $self->{full_name} = $attrs->{first} . ' ' . $attrs->{last};
212             }
213              
214             package Employee;
215             use Class;
216             extends 'Person';
217              
218             sub BUILD {
219             my ($self, $attrs) = @_;
220             $self->{employee_id} = $attrs->{id};
221             }
222              
223             # Create an object
224             my $emp = Employee->new(first => 'John', last => 'Doe', id => 123);
225              
226             print $emp->{full_name}; # John Doe
227             print $emp->{employee_id}; # 123
228              
229             # Using roles if Role.pm is available
230             package Manager;
231             use Class;
232             with 'SomeRole';
233             my $mgr = Manager->new();
234              
235             =head1 DESCRIPTION
236              
237             Class provides a lightweight Perl object system with:
238              
239             =over 4
240              
241             =item * Parent-first constructor building via C methods.
242              
243             =item * Simple inheritance via C with method copying.
244              
245             =item * Optional role consumption via C and C (if C module is available).
246              
247             =item * Automatic caching of BUILD order for efficient object creation.
248              
249             =item * Optimized method copying for better performance.
250              
251             =back
252              
253             This module includes performance optimizations such as cached BUILD method resolution,
254             efficient parent class loading, and optimized method copying with caching.
255              
256             =cut
257              
258             =head1 BUILD METHODS
259              
260             Classes can define a C method:
261              
262             sub BUILD {
263             my ($self, $attrs) = @_;
264             # initialize object
265             }
266              
267             All BUILD methods in the inheritance chain are called in parent-first order during C. The order is determined by depth-first traversal, ensuring that parent classes are always initialized before their children.
268              
269             For diamond inheritance patterns:
270              
271             A
272             / \
273             B C
274             \ /
275             D
276              
277             BUILD methods are called in the order: A, B, C, D (true parent-first order)
278              
279             =head1 METHOD COPYING
280              
281             This system copies public methods from parent classes to child classes. This design enables:
282              
283             =over 4
284              
285             =item * Direct method access in child symbol tables
286              
287             =item * Proper functioning of object cloning
288              
289             =item * Better performance for frequently called methods
290              
291             =item * Compatibility with code that expects direct method access
292              
293             =back
294              
295             The following methods are NOT copied:
296              
297             =over 4
298              
299             =item * Special methods (BUILD, new, extends, with, does, import, AUTOLOAD, DESTROY)
300              
301             =item * Private methods (starting with underscore)
302              
303             =item * Package metadata (ISA, VERSION, EXPORT, etc.)
304              
305             =back
306              
307             =head1 ROLES
308              
309             If a C module is available, you can consume roles via:
310              
311             with 'RoleName';
312             does 'RoleName';
313              
314             This provides role-based composition for shared behavior. The Role module must be installed separately.
315              
316             =head1 PERFORMANCE OPTIMISATIONS
317              
318             This version includes significant performance improvements:
319              
320             =over 4
321              
322             =item * Cached BUILD method resolution using depth-first parent-first order
323              
324             =item * Precomputed skip patterns for fast method filtering
325              
326             =item * Method copying cache to avoid duplicate operations
327              
328             =item * Efficient parent class loading with minimal overhead
329              
330             =item * Optimized symbol table scanning
331              
332             =back
333              
334             =head1 CACHING
335              
336             Class uses internal caches to optimise performance:
337              
338             =over 4
339              
340             =item * C<%BUILD_METHODS_CACHE> - caches linearised parent-first build order
341              
342             =item * C<%METHOD_COPY_CACHE> - tracks which parent-child pairs have had methods copied
343              
344             =back
345              
346             Caches are automatically updated when inheritance changes via C.
347              
348             =head1 ERROR HANDLING
349              
350             =over 4
351              
352             =item * Recursive inheritance is detected and throws an exception.
353              
354             =item * Failure to load a parent class is non-fatal (parent might be defined inline).
355              
356             =back
357              
358             =head1 EXAMPLES
359              
360             =head2 Basic Inheritance with Method Copying
361              
362             package Animal;
363             use Class;
364             sub speak { "animal sound" }
365             sub eat { "eating" }
366              
367             package Dog;
368             use Class;
369             extends 'Animal';
370             sub speak { "woof" } # Overrides parent method
371              
372             my $dog = Dog->new;
373             print $dog->speak; # "woof" (from Dog)
374             print $dog->eat; # "eating" (copied from Animal)
375              
376             # Method is copied to Dog's symbol table
377             no strict 'refs';
378             print defined &Dog::eat ? "copied" : "not copied"; # "copied"
379              
380             =head2 Diamond Inheritance
381              
382             package A;
383             use Class;
384             sub BUILD { print "A BUILD\n" }
385              
386             package B;
387             use Class;
388             extends 'A';
389             sub BUILD { print "B BUILD\n" }
390              
391             package C;
392             use Class;
393             extends 'A';
394             sub BUILD { print "C BUILD\n" }
395              
396             package D;
397             use Class;
398             extends 'B', 'C';
399             sub BUILD { print "D BUILD\n" }
400              
401             my $d = D->new;
402             # Output: A BUILD, B BUILD, C BUILD, D BUILD
403              
404             =head2 Object Cloning with Method Copying
405              
406             package Base;
407             use Class;
408             sub clone_method { "works" }
409              
410             package Child;
411             use Class;
412             extends 'Base';
413              
414             my $original = Child->new;
415             my $cloned = bless { %$original }, ref($original);
416              
417             # Works because methods are copied to Child
418             print $cloned->clone_method; # "works"
419              
420             =head1 METHODS
421              
422             =head2 new
423              
424             my $obj = Class->new(%attributes);
425              
426             Constructs a new object of the class, calling all C methods from parent classes in parent-first order. All attributes are passed to C as a hashref.
427              
428             The constructor uses cached BUILD method references for optimal performance, especially in deep inheritance hierarchies.
429              
430             =cut
431              
432             =head2 _compute_build_methods
433              
434             my $build_methods = _compute_build_methods($class);
435              
436             Internal method that computes the BUILD methods in parent-first order using depth-first traversal.
437              
438             This ensures BUILD methods are called from the root parent down to the child class, which is essential for proper initialisation in inheritance hierarchies.
439              
440             =cut
441              
442             =head2 _depth_first_traverse
443              
444             _depth_first_traverse($class, \@order, \%visited);
445              
446             Internal recursive method that performs depth-first traversal of the inheritance hierarchy.
447              
448             This method ensures that parent classes are always processed before their children, which is crucial for correct BUILD method ordering.
449              
450             =cut
451              
452             =head2 extends
453              
454             extends 'ParentClass';
455             extends 'Parent1', 'Parent2';
456              
457             Adds one or more parent classes to the calling class. This method:
458              
459             =over 4
460              
461             =item * Automatically loads parent classes if not already loaded
462              
463             =item * Prevents recursive inheritance
464              
465             =item * Copies public methods from parents to children
466              
467             =item * Maintains inheritance via C<@ISA>
468              
469             =item * Clears relevant caches to ensure consistency
470              
471             =back
472              
473             Method copying is performed to ensure that inherited methods are directly available in the child class's symbol table, which enables features like object cloning to work correctly.
474              
475             =cut
476              
477             =head2 _copy_public_methods
478              
479             _copy_public_methods($child_class, $parent_class);
480              
481             Internal method that copies public methods from parent to child class. This method:
482              
483             =over 4
484              
485             =item * Skips special methods (BUILD, new, extends, etc.)
486              
487             =item * Skips private methods (starting with underscore)
488              
489             =item * Uses caching to avoid duplicate copying
490              
491             =item * Only copies methods not already defined in child
492              
493             =back
494              
495             This optimised implementation uses precomputed skip patterns and caching for better performance.
496              
497             =cut
498              
499             =head2 _delete_build_cache
500              
501             _delete_build_cache($class);
502              
503             Internal method that clears the BUILD methods cache for a class and all classes that inherit from it.
504              
505             This ensures cache consistency when inheritance relationships change. Also clears method copy caches for affected classes.
506              
507             =cut
508              
509             =head2 _inherits_from
510              
511             _inherits_from($class, $parent);
512              
513             Internal recursive method that checks if a class inherits from another class, either directly or indirectly.
514              
515             Returns true if C<$class> inherits from C<$parent>, false otherwise.
516              
517             =cut
518              
519             =head1 IMPORT
520              
521             use Class;
522             use Class 'extends' => 'Parent';
523              
524             When imported, Class automatically installs the following functions into the caller's namespace:
525              
526             =over 4
527              
528             =item * C - constructor
529              
530             =item * C - inheritance helper
531              
532             =item * C and C - if Role.pm is available
533              
534             =back
535              
536             Optionally, you can specify C in the import statement to immediately set a parent class:
537              
538             use Class 'extends' => 'Parent';
539              
540             The import method also enables L and L in the calling package.
541              
542             =cut
543              
544             =head1 AUTHOR
545              
546             Mohammad Sajid Anwar, C<< >>
547              
548             =head1 REPOSITORY
549              
550             L
551              
552             =head1 BUGS
553              
554             Please report any bugs or feature requests through the web interface at L.
555             I will be notified and then you'll automatically be notified of progress on your bug as I make changes.
556              
557             =head1 SUPPORT
558              
559             You can find documentation for this module with the perldoc command.
560              
561             perldoc Class
562              
563             You can also look for information at:
564              
565             =over 4
566              
567             =item * BUG Report
568              
569             L
570              
571             =back
572              
573             =head1 LICENSE AND COPYRIGHT
574              
575             Copyright (C) 2025 Mohammad Sajid Anwar.
576              
577             This program is free software; you can redistribute it and / or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:
578              
579             L
580              
581             Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.
582              
583             If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.
584              
585             This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.
586              
587             This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.
588              
589             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
590              
591             =cut
592              
593             1; # End of Class