1
+ <?php
2
+
3
+ /*
4
+ * Copyright (c) 2025. Encore Digital Group.
5
+ * All Rights Reserved.
6
+ */
7
+
8
+ namespace EncoreDigitalGroup \StdLib \Objects \Support \Traits ;
9
+
10
+ use BadMethodCallException ;
11
+ use Closure ;
12
+ use Illuminate \Database \Eloquent \Model ;
13
+ use ReflectionClass ;
14
+ use ReflectionMethod ;
15
+
16
+ /** @experimental */
17
+ trait Macroable
18
+ {
19
+ protected static array $ macros = [];
20
+
21
+ public static function macro (string $ name , object |callable $ macro ): void
22
+ {
23
+ static ::$ macros [$ name ] = $ macro ;
24
+ }
25
+
26
+ public static function mixin (object $ mixin , bool $ replace = true ): void
27
+ {
28
+ $ methods = (new ReflectionClass ($ mixin ))->getMethods (
29
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
30
+ );
31
+
32
+ foreach ($ methods as $ method ) {
33
+ if ($ replace || !static ::hasMacro ($ method ->name )) {
34
+ static ::macro ($ method ->name , $ method ->invoke ($ mixin ));
35
+ }
36
+ }
37
+ }
38
+
39
+ public static function hasMacro (string $ name ): bool
40
+ {
41
+ return isset (static ::$ macros [$ name ]);
42
+ }
43
+
44
+ public static function flushMacros (): void
45
+ {
46
+ static ::$ macros = [];
47
+ }
48
+
49
+ protected static function canDeferToLaravel (): bool
50
+ {
51
+ return class_exists (Model::class) &&
52
+ is_subclass_of (static ::class, Model::class);
53
+ }
54
+
55
+ /**
56
+ * Defer static method call to the parent class's __callStatic for Eloquent models.
57
+ *
58
+ * @return mixed
59
+ */
60
+ protected static function deferToLaravel (string $ method , array $ parameters , bool $ static = false )
61
+ {
62
+ if (static ::canDeferToLaravel ()) {
63
+ // Defer to the parent class's __callStatic (e.g., Model::__callStatic)
64
+ $ parentClass = get_parent_class (static ::class);
65
+ if ($ parentClass && (new ReflectionClass ($ parentClass ))->hasMethod ("__callStatic " )) {
66
+ return parent ::__callStatic ($ method , $ parameters );
67
+ }
68
+ }
69
+
70
+ throw new BadMethodCallException (sprintf ("Method %s::%s does not exist. " , static ::class, $ method ));
71
+ }
72
+
73
+ /**
74
+ * Defer instance method call to the parent class's __call for Eloquent models.
75
+ */
76
+ protected function deferToEloquentInstance (string $ method , array $ parameters ): mixed
77
+ {
78
+ if (static ::canDeferToLaravel ()) {
79
+ // Defer to the parent class's __call (e.g., Model::__call)
80
+ $ parentClass = get_parent_class ($ this );
81
+ if ($ parentClass && (new ReflectionClass ($ parentClass ))->hasMethod ("__call " )) {
82
+ return parent ::__call ($ method , $ parameters );
83
+ }
84
+ }
85
+
86
+ throw new BadMethodCallException (sprintf ("Method %s::%s does not exist. " , static ::class, $ method ));
87
+ }
88
+
89
+ public static function __callStatic (string $ method , array $ parameters ): mixed
90
+ {
91
+ // Check if a macro exists for the method
92
+ if (static ::hasMacro ($ method )) {
93
+ $ macro = static ::$ macros [$ method ];
94
+
95
+ if ($ macro instanceof Closure) {
96
+ $ macro = $ macro ->bindTo (null , static ::class);
97
+ }
98
+
99
+ return $ macro (...$ parameters );
100
+ }
101
+
102
+ // Defer to the parent class's __callStatic if it exists
103
+ $ parentClass = get_parent_class (static ::class);
104
+ if ($ parentClass && (new ReflectionClass ($ parentClass ))->hasMethod ("__callStatic " )) {
105
+ return parent ::__callStatic ($ method , $ parameters );
106
+ }
107
+
108
+ // If the class is an Eloquent model, defer to Laravel's handling
109
+ if (static ::canDeferToLaravel ()) {
110
+ return static ::deferToLaravel ($ method , $ parameters , true );
111
+ }
112
+
113
+ throw new BadMethodCallException (sprintf ("Method %s::%s does not exist. " , static ::class, $ method ));
114
+ }
115
+
116
+ public function __call (string $ method , array $ parameters ): mixed
117
+ {
118
+ // Check if a macro exists for the method
119
+ if (static ::hasMacro ($ method )) {
120
+ $ macro = static ::$ macros [$ method ];
121
+
122
+ if ($ macro instanceof Closure) {
123
+ $ macro = $ macro ->bindTo ($ this , static ::class);
124
+ }
125
+
126
+ return $ macro (...$ parameters );
127
+ }
128
+
129
+ // Defer to the parent class's __call if it exists
130
+ $ parentClass = get_parent_class ($ this );
131
+ if ($ parentClass && (new ReflectionClass ($ parentClass ))->hasMethod ("__call " )) {
132
+ return parent ::__call ($ method , $ parameters );
133
+ }
134
+
135
+ // If the class is an Eloquent model, defer to Laravel's handling
136
+ if (static ::canDeferToLaravel ()) {
137
+ return $ this ->deferToEloquentInstance ($ method , $ parameters );
138
+ }
139
+
140
+ throw new BadMethodCallException (sprintf ("Method %s::%s does not exist. " , static ::class, $ method ));
141
+ }
142
+ }
0 commit comments