Commit | Line | Data |
5ab72593 |
1 | package DBIx::Class::MethodAttributes; |
5f48fa56 |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use DBIx::Class::_Util qw( uniq refdesc visit_namespaces ); |
7 | use Scalar::Util qw( weaken refaddr ); |
8 | |
5f48fa56 |
9 | use namespace::clean; |
10 | |
5ab72593 |
11 | my ( $attr_cref_registry, $attr_cache_active ); |
5f48fa56 |
12 | sub DBIx::Class::__Attr_iThreads_handler__::CLONE { |
13 | |
14 | # This is disgusting, but the best we can do without even more surgery |
5ab72593 |
15 | # Note the if() at the end - we do not run this crap if we can help it |
5f48fa56 |
16 | visit_namespaces( action => sub { |
17 | my $pkg = shift; |
18 | |
19 | # skip dangerous namespaces |
20 | return 1 if $pkg =~ /^ (?: |
21 | DB | next | B | .+? ::::ISA (?: ::CACHE ) | Class::C3 |
22 | ) $/x; |
23 | |
24 | no strict 'refs'; |
25 | |
26 | if ( |
27 | exists ${"${pkg}::"}{__cag___attr_cache} |
28 | and |
29 | ref( my $attr_stash = ${"${pkg}::__cag___attr_cache"} ) eq 'HASH' |
30 | ) { |
31 | $attr_stash->{ $attr_cref_registry->{$_}{weakref} } = delete $attr_stash->{$_} |
32 | for keys %$attr_stash; |
33 | } |
34 | |
35 | return 1; |
5ab72593 |
36 | }) if $attr_cache_active; |
5f48fa56 |
37 | |
38 | # renumber the cref registry itself |
39 | %$attr_cref_registry = map { |
40 | ( defined $_->{weakref} ) |
41 | ? ( |
42 | # because of how __attr_cache works, ugh |
43 | "$_->{weakref}" => $_, |
44 | ) |
45 | : () |
46 | } values %$attr_cref_registry; |
47 | } |
48 | |
49 | sub MODIFY_CODE_ATTRIBUTES { |
5ab72593 |
50 | my $class = shift; |
51 | my $code = shift; |
52 | |
53 | my $attrs; |
54 | $attrs->{ |
55 | $_ =~ /^[a-z]+$/ ? 'builtin' |
56 | : $_ =~ /^DBIC_/ ? 'dbic' |
57 | : 'misc' |
58 | }{$_}++ for @_; |
59 | |
5f48fa56 |
60 | |
61 | # compaction step |
62 | defined $attr_cref_registry->{$_}{weakref} or delete $attr_cref_registry->{$_} |
63 | for keys %$attr_cref_registry; |
64 | |
65 | # The original misc-attr API used stringification instead of refaddr - can't change that now |
66 | if( $attr_cref_registry->{$code} ) { |
67 | Carp::confess( sprintf |
68 | "Coderefs '%s' and '%s' stringify to the same value '%s': nothing will work", |
69 | refdesc($code), |
70 | refdesc($attr_cref_registry->{$code}{weakref}), |
71 | "$code" |
72 | ) if refaddr($attr_cref_registry->{$code}{weakref}) != refaddr($code); |
73 | } |
74 | else { |
75 | weaken( $attr_cref_registry->{$code}{weakref} = $code ) |
76 | } |
77 | |
296248c3 |
78 | |
79 | # increment the pkg gen, this ensures the sanity checkers will re-evaluate |
80 | # this class when/if the time comes |
81 | mro::method_changed_in($class) if ( |
82 | ! DBIx::Class::_ENV_::OLD_MRO |
83 | and |
84 | ( $attrs->{dbic} or $attrs->{misc} ) |
85 | ); |
86 | |
87 | |
5ab72593 |
88 | # handle legacy attrs |
89 | if( $attrs->{misc} ) { |
90 | |
91 | # if the user never tickles this - we won't have to do a gross |
92 | # symtable scan in the ithread handler above, so: |
93 | # |
94 | # User - please don't tickle this |
95 | $attr_cache_active = 1; |
96 | |
97 | $class->mk_classaccessor('__attr_cache' => {}) |
98 | unless $class->can('__attr_cache'); |
99 | |
100 | $class->__attr_cache->{$code} = [ sort( uniq( |
101 | @{ $class->__attr_cache->{$code} || [] }, |
102 | keys %{ $attrs->{misc} }, |
103 | ))]; |
104 | } |
105 | |
296248c3 |
106 | |
5ab72593 |
107 | # handle DBIC_* attrs |
108 | if( $attrs->{dbic} ) { |
109 | my $slot = $attr_cref_registry->{$code}; |
110 | |
111 | $slot->{attrs} = [ uniq |
112 | @{ $slot->{attrs} || [] }, |
113 | grep { |
114 | $class->VALID_DBIC_CODE_ATTRIBUTE($_) |
115 | or |
116 | Carp::confess( "DBIC-specific attribute '$_' did not pass validation by $class->VALID_DBIC_CODE_ATTRIBUTE() as described in DBIx::Class::MethodAttributes" ) |
117 | } keys %{$attrs->{dbic}}, |
118 | ]; |
119 | } |
5f48fa56 |
120 | |
296248c3 |
121 | |
5f48fa56 |
122 | # FIXME - DBIC essentially gobbles up any attribute it can lay its hands on: |
123 | # decidedly not cool |
124 | # |
125 | # There should be some sort of warning on unrecognized attributes or |
126 | # somesuch... OTOH people do use things in the wild hence the plan of action |
127 | # is anything but clear :/ |
128 | # |
129 | # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/lib/DBIx/Class/Service.pm#L93-110 |
130 | # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L29 |
131 | # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L36 |
132 | # |
5ab72593 |
133 | # For the time being reuse the old logic for any attribute we do not have |
134 | # explicit plans for (i.e. stuff that is neither reserved, nor DBIC-internal) |
135 | # |
136 | # Pass the "builtin attrs" onwards, as the DBIC internals can't possibly handle them |
137 | return sort keys %{ $attrs->{builtin} || {} }; |
138 | } |
139 | |
140 | # Address the above FIXME halfway - if something (e.g. DBIC::Helpers) wants to |
141 | # add extra attributes - it needs to override this in its base class to allow |
142 | # for 'return 1' on the newly defined attributes |
143 | sub VALID_DBIC_CODE_ATTRIBUTE { |
144 | #my ($class, $attr) = @_; |
145 | |
1b822bd3 |
146 | ### |
147 | ### !!! IMPORTANT !!! |
148 | ### |
149 | ### *DO NOT* yield to the temptation of using free-form-argument attributes. |
150 | ### The technique was proven instrumental in Catalyst a decade ago, and |
151 | ### was more recently revived in Sub::Attributes. Yet, while on the surface |
152 | ### they seem immensely useful, per-attribute argument lists are in fact an |
153 | ### architectural dead end. |
154 | ### |
155 | ### In other words: you are *very strongly urged* to ensure the regex below |
156 | ### does not allow anything beyond qr/^ DBIC_method_is_ [A-Z_a-z0-9]+ $/x |
157 | ### |
158 | |
159 | $_[1] =~ /^ DBIC_method_is_ (?: |
160 | indirect_sugar |
09d8fb4a |
161 | | |
162 | generated_from_resultsource_metadata |
163 | | |
164 | (?: inflated_ | filtered_ )? column_ (?: extra_)? accessor |
165 | | |
166 | single_relationship_accessor |
167 | | |
168 | (?: multi | filter ) _relationship_ (?: extra_ )? accessor |
169 | | |
170 | proxy_to_relationship |
171 | | |
172 | m2m_ (?: extra_)? sugar (?:_with_attrs)? |
1b822bd3 |
173 | ) $/x; |
5f48fa56 |
174 | } |
175 | |
176 | sub FETCH_CODE_ATTRIBUTES { |
5ab72593 |
177 | #my ($class,$code) = @_; |
178 | |
179 | sort( |
180 | @{ $_[0]->_attr_cache->{$_[1]} || [] }, |
181 | ( defined( $attr_cref_registry->{$_[1]}{ weakref } ) |
182 | ? @{ $attr_cref_registry->{$_[1]}{attrs} || [] } |
183 | : () |
184 | ), |
185 | ) |
5f48fa56 |
186 | } |
187 | |
188 | sub _attr_cache { |
189 | my $self = shift; |
190 | +{ |
191 | %{ $self->can('__attr_cache') ? $self->__attr_cache : {} }, |
192 | %{ $self->maybe::next::method || {} }, |
193 | }; |
194 | } |
195 | |
196 | 1; |
5ab72593 |
197 | |
198 | __END__ |
199 | |
200 | =head1 NAME |
201 | |
202 | DBIx::Class::MethodAttributes - DBIC-specific handling of CODE attributes |
203 | |
204 | =head1 SYNOPSIS |
205 | |
206 | my @attrlist = attributes::get( \&My::App::Schema::Result::some_method ) |
207 | |
208 | =head1 DESCRIPTION |
209 | |
210 | This class provides the L<DBIx::Class> inheritance chain with the bits |
211 | necessary for L<attribute|attributes> support on methods. |
212 | |
213 | Historically DBIC has accepted any string as a C<CODE> attribute and made |
214 | such strings available via the semi-private L</_attr_cache> method. This |
215 | was used for e.g. the long-deprecated L<DBIx::Class::ResultSetManager>, |
216 | but also has evidence of use on both C<CPAN> and C<DarkPAN>. |
217 | |
218 | Starting mid-2016 DBIC treats any method attribute starting with C<DBIC_> |
219 | as an I<internal boolean decorator> for various DBIC-related methods. |
220 | Unlike the general attribute naming policy, strict whitelisting is imposed |
221 | on attribute names starting with C<DBIC_> as described in |
222 | L</VALID_DBIC_CODE_ATTRIBUTE> below. |
223 | |
224 | =head2 DBIC-specific method attributes |
225 | |
226 | The following method attributes are currently recognized under the C<DBIC_*> |
227 | prefix: |
228 | |
1b822bd3 |
229 | =head3 DBIC_method_is_indirect_sugar |
5ab72593 |
230 | |
1b822bd3 |
231 | The presence of this attribute indicates a helper "sugar" method. Overriding |
232 | such methods in your subclasses will be of limited success at best, as DBIC |
233 | itself and various plugins are much more likely to invoke alternative direct |
234 | call paths, bypassing your override entirely. Good examples of this are |
235 | L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>. |
5ab72593 |
236 | |
12e7015a |
237 | See also the check |
238 | L<DBIx::Class::Schema::SanityChecker/no_indirect_method_overrides>. |
239 | |
09d8fb4a |
240 | =head3 DBIC_method_is_generated_from_resultsource_metadata |
241 | |
242 | This attribute is applied to all methods dynamically installed after various |
243 | invocations of L<ResultSource metadata manipulation |
244 | methods|DBIx::Class::Manual::ResultClass/DBIx::Class::ResultSource>. Notably |
245 | this includes L<add_columns|DBIx::Class::ResultSource/add_columns>, |
246 | L<add_relationship|DBIx::Class::ResultSource/add_relationship>, |
247 | L<the proxied relationship attribute|DBIx::Class::Relationship::Base/proxy> |
248 | and the various L<relationship |
249 | helpers|DBIx::Class::Manual::ResultClass/DBIx::Class::Relationship>, |
250 | B<except> the L<M2M helper|DBIx::Class::Relationship/many_to_many> (given its |
251 | effects are never reflected as C<ResultSource metadata>). |
252 | |
253 | =head3 DBIC_method_is_column_accessor |
254 | |
255 | This attribute is applied to all methods dynamically installed as a result of |
256 | invoking L<add_columns|DBIx::Class::ResultSource/add_columns>. |
257 | |
258 | =head3 DBIC_method_is_inflated_column_accessor |
259 | |
260 | This attribute is applied to all methods dynamically installed as a result of |
261 | invoking L<inflate_column|DBIx::Class::InflateColumn/inflate_column>. |
262 | |
263 | =head3 DBIC_method_is_filtered_column_accessor |
264 | |
265 | This attribute is applied to all methods dynamically installed as a result of |
266 | invoking L<filter_column|DBIx::Class::FilterColumn/filter_column>. |
267 | |
268 | =head3 DBIC_method_is_*column_extra_accessor |
269 | |
270 | For historical reasons any L<Class::Accessor::Grouped> accessor is generated |
271 | twice as C<{name}> and C<_{name}_accessor>. The second method is marked with |
272 | C<DBIC_method_is_*column_extra_accessor> correspondingly. |
273 | |
274 | =head3 DBIC_method_is_single_relationship_accessor |
275 | |
276 | This attribute is applied to all methods dynamically installed as a result of |
277 | invoking L<might_have|DBIx::Class::Relationship/might_have>, |
278 | L<has_one|DBIx::Class::Relationship/has_one> or |
279 | L<belongs_to|DBIx::Class::Relationship/belongs_to> (though for C<belongs_to> |
280 | see L<...filter_rel...|/DBIC_method_is_filter_relationship_accessor> below. |
281 | |
282 | =head3 DBIC_method_is_multi_relationship_accessor |
283 | |
284 | This attribute is applied to the main method dynamically installed as a result |
285 | of invoking L<has_many|DBIx::Class::Relationship/has_many>. |
286 | |
287 | =head3 DBIC_method_is_multi_relationship_extra_accessor |
288 | |
289 | This attribute is applied to the two extra methods dynamically installed as a |
290 | result of invoking L<has_many|DBIx::Class::Relationship/has_many>: |
291 | C<$relname_rs> and C<add_to_$relname>. |
292 | |
293 | =head3 DBIC_method_is_filter_relationship_accessor |
294 | |
295 | This attribute is applied to (legacy) methods dynamically installed as a |
296 | result of invoking L<belongs_to|DBIx::Class::Relationship/belongs_to> with an |
297 | already-existing identically named column. The method is internally |
298 | implemented as an L<inflated_column|/DBIC_method_is_inflated_column_accessor> |
299 | and is labeled with both atributes at the same time. |
300 | |
301 | =head3 DBIC_method_is_filter_relationship_extra_accessor |
302 | |
303 | Same as L</DBIC_method_is_*column_extra_accessor>. |
304 | |
305 | =head3 DBIC_method_is_proxy_to_relationship |
306 | |
307 | This attribute is applied to methods dynamically installed as a result of |
308 | providing L<the proxied relationship |
309 | attribute|DBIx::Class::Relationship::Base/proxy>. |
310 | |
311 | =head3 DBIC_method_is_m2m_sugar |
312 | |
313 | =head3 DBIC_method_is_m2m_sugar_with_attrs |
314 | |
315 | One of the above attributes is applied to the main method dynamically |
316 | installed as a result of invoking |
317 | L<many_to_many|DBIx::Class::Relationship/many_to_many>. The C<_with_atrs> suffix |
318 | serves to indicate whether the user supplied any C<\%attrs> to the |
319 | C<many_to_many> call. There is deliberately no mechanism to retrieve the actual |
320 | supplied values: if you really need this functionality you would need to rely on |
321 | L<DBIx::Class::IntrospectableM2M>. |
322 | |
323 | =head3 DBIC_method_is_extra_m2m_sugar |
324 | |
325 | =head3 DBIC_method_is_extra_m2m_sugar_with_attrs |
326 | |
327 | One of the above attributes is applied to the extra B<four> methods dynamically |
328 | installed as a result of invoking |
329 | L<many_to_many|DBIx::Class::Relationship/many_to_many>: C<$m2m_rs>, C<add_to_$m2m>, |
330 | C<remove_from_$m2m> and C<set_$m2m>. |
331 | |
5ab72593 |
332 | =head1 METHODS |
333 | |
334 | =head2 MODIFY_CODE_ATTRIBUTES |
335 | |
336 | See L<attributes/MODIFY_type_ATTRIBUTES>. |
337 | |
338 | =head2 FETCH_CODE_ATTRIBUTES |
339 | |
340 | See L<attributes/FETCH_type_ATTRIBUTES>. Always returns the combination of |
341 | all attributes: both the free-form strings registered via the |
342 | L<legacy system|/_attr_cache> and the DBIC-specific ones. |
343 | |
344 | =head2 VALID_DBIC_CODE_ATTRIBUTE |
345 | |
346 | =over |
347 | |
348 | =item Arguments: $attribute_string |
349 | |
350 | =item Return Value: ( true| false ) |
351 | |
352 | =back |
353 | |
354 | This method is invoked when processing each DBIC-specific attribute (the ones |
355 | starting with C<DBIC_>). An attribute is considered invalid and an exception |
356 | is thrown unless this method returns a C<truthy> value. |
357 | |
358 | =head2 _attr_cache |
359 | |
360 | =over |
361 | |
362 | =item Arguments: none |
363 | |
364 | =item Return Value: B<purposefully undocumented> |
365 | |
366 | =back |
367 | |
368 | The legacy method of retrieving attributes declared on DBIC methods |
369 | (L</FETCH_CODE_ATTRIBUTES> was not defined until mid-2016). This method |
370 | B<does not return any DBIC-specific attributes>, and is kept for backwards |
371 | compatibility only. |
372 | |
373 | In order to query the attributes of a particular method use |
374 | L<attributes::get()|attributes/get> as shown in the L</SYNOPSIS>. |
375 | |
376 | =head1 FURTHER QUESTIONS? |
377 | |
378 | Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>. |
379 | |
380 | =head1 COPYRIGHT AND LICENSE |
381 | |
382 | This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE> |
383 | by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can |
384 | redistribute it and/or modify it under the same terms as the |
385 | L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>. |