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 | | |
28ef9468 |
162 | (?: bypassable | mandatory ) _resultsource_proxy |
163 | | |
09d8fb4a |
164 | generated_from_resultsource_metadata |
165 | | |
166 | (?: inflated_ | filtered_ )? column_ (?: extra_)? accessor |
167 | | |
168 | single_relationship_accessor |
169 | | |
170 | (?: multi | filter ) _relationship_ (?: extra_ )? accessor |
171 | | |
172 | proxy_to_relationship |
173 | | |
174 | m2m_ (?: extra_)? sugar (?:_with_attrs)? |
1b822bd3 |
175 | ) $/x; |
5f48fa56 |
176 | } |
177 | |
178 | sub FETCH_CODE_ATTRIBUTES { |
5ab72593 |
179 | #my ($class,$code) = @_; |
180 | |
181 | sort( |
182 | @{ $_[0]->_attr_cache->{$_[1]} || [] }, |
183 | ( defined( $attr_cref_registry->{$_[1]}{ weakref } ) |
184 | ? @{ $attr_cref_registry->{$_[1]}{attrs} || [] } |
185 | : () |
186 | ), |
187 | ) |
5f48fa56 |
188 | } |
189 | |
190 | sub _attr_cache { |
191 | my $self = shift; |
192 | +{ |
193 | %{ $self->can('__attr_cache') ? $self->__attr_cache : {} }, |
194 | %{ $self->maybe::next::method || {} }, |
195 | }; |
196 | } |
197 | |
198 | 1; |
5ab72593 |
199 | |
200 | __END__ |
201 | |
202 | =head1 NAME |
203 | |
204 | DBIx::Class::MethodAttributes - DBIC-specific handling of CODE attributes |
205 | |
206 | =head1 SYNOPSIS |
207 | |
208 | my @attrlist = attributes::get( \&My::App::Schema::Result::some_method ) |
209 | |
210 | =head1 DESCRIPTION |
211 | |
212 | This class provides the L<DBIx::Class> inheritance chain with the bits |
213 | necessary for L<attribute|attributes> support on methods. |
214 | |
215 | Historically DBIC has accepted any string as a C<CODE> attribute and made |
216 | such strings available via the semi-private L</_attr_cache> method. This |
217 | was used for e.g. the long-deprecated L<DBIx::Class::ResultSetManager>, |
218 | but also has evidence of use on both C<CPAN> and C<DarkPAN>. |
219 | |
220 | Starting mid-2016 DBIC treats any method attribute starting with C<DBIC_> |
221 | as an I<internal boolean decorator> for various DBIC-related methods. |
222 | Unlike the general attribute naming policy, strict whitelisting is imposed |
223 | on attribute names starting with C<DBIC_> as described in |
224 | L</VALID_DBIC_CODE_ATTRIBUTE> below. |
225 | |
226 | =head2 DBIC-specific method attributes |
227 | |
228 | The following method attributes are currently recognized under the C<DBIC_*> |
229 | prefix: |
230 | |
1b822bd3 |
231 | =head3 DBIC_method_is_indirect_sugar |
5ab72593 |
232 | |
1b822bd3 |
233 | The presence of this attribute indicates a helper "sugar" method. Overriding |
234 | such methods in your subclasses will be of limited success at best, as DBIC |
235 | itself and various plugins are much more likely to invoke alternative direct |
236 | call paths, bypassing your override entirely. Good examples of this are |
237 | L<DBIx::Class::ResultSet/create> and L<DBIx::Class::Schema/connect>. |
5ab72593 |
238 | |
12e7015a |
239 | See also the check |
240 | L<DBIx::Class::Schema::SanityChecker/no_indirect_method_overrides>. |
241 | |
28ef9468 |
242 | =head3 DBIC_method_is_mandatory_resultsource_proxy |
243 | |
244 | =head3 DBIC_method_is_bypassable_resultsource_proxy |
245 | |
246 | The presence of one of these attributes on a L<proxied ResultSource |
247 | method|DBIx::Class::Manual::ResultClass/DBIx::Class::ResultSource> indicates |
248 | how DBIC will behave when someone calls e.g.: |
249 | |
250 | $some_result->result_source->add_columns(...) |
251 | |
252 | as opposed to the conventional |
253 | |
254 | SomeResultClass->add_columns(...) |
255 | |
256 | This distinction becomes important when someone declares a sub named after |
257 | one of the (currently 22) methods proxied from a |
258 | L<Result|DBIx::Class::Manual::ResultClass> to |
259 | L<ResultSource|DBIx::Class::ResultSource>. While there are obviously no |
260 | problems when these methods are called at compile time, there is a lot of |
261 | ambiguity whether an override of something like |
262 | L<columns_info|DBIx::Class::ResultSource/columns_info> will be respected by |
263 | DBIC and various plugins during runtime operations. |
264 | |
265 | It must be noted that there is a reason for this weird situation: during the |
266 | original design of DBIC the "ResultSourceProxy" system was established in |
267 | order to allow easy transition from Class::DBI. Unfortunately it was not |
268 | well abstracted away: it is rather difficult to use a custom ResultSource |
269 | subclass. The expansion of the DBIC project never addressed this properly |
270 | in the years since. As a result when one wishes to override a part of the |
271 | ResultSource functionality, the overwhelming practice is to hook a method |
272 | in a Result class and "hope for the best". |
273 | |
274 | The subtle changes of various internal call-chains in C<DBIC v0.0829xx> make |
275 | this silent uncertainty untenable. As a solution any such override will now |
276 | issue a descriptive warning that it has been bypassed during a |
277 | C<< $rsrc->overriden_function >> invocation. A user B<must> determine how |
278 | each individual override must behave in this situation, and tag it with one |
279 | of the above two attributes. |
280 | |
281 | Naturally any override marked with C<..._bypassable_resultsource_proxy> will |
282 | behave like it did before: it will be silently ignored. This is the attribute |
283 | you want to set if your code appears to work fine, and you do not wish to |
284 | receive the warning anymore (though you are strongly encouraged to understand |
285 | the other option). |
286 | |
287 | However overrides marked with C<..._mandatory_resultsource_proxy> will always |
288 | be reinvoked by DBIC itself, so that any call of the form: |
289 | |
290 | $some_result->result_source->columns_info(...) |
291 | |
292 | will be transformed into: |
293 | |
294 | $some_result->result_source->result_class->columns_info(...) |
295 | |
296 | with the rest of the callchain flowing out of that (provided the override did |
297 | invoke L<next::method|mro/next::method> where appropriate) |
298 | |
09d8fb4a |
299 | =head3 DBIC_method_is_generated_from_resultsource_metadata |
300 | |
301 | This attribute is applied to all methods dynamically installed after various |
302 | invocations of L<ResultSource metadata manipulation |
303 | methods|DBIx::Class::Manual::ResultClass/DBIx::Class::ResultSource>. Notably |
304 | this includes L<add_columns|DBIx::Class::ResultSource/add_columns>, |
305 | L<add_relationship|DBIx::Class::ResultSource/add_relationship>, |
306 | L<the proxied relationship attribute|DBIx::Class::Relationship::Base/proxy> |
307 | and the various L<relationship |
308 | helpers|DBIx::Class::Manual::ResultClass/DBIx::Class::Relationship>, |
309 | B<except> the L<M2M helper|DBIx::Class::Relationship/many_to_many> (given its |
310 | effects are never reflected as C<ResultSource metadata>). |
311 | |
312 | =head3 DBIC_method_is_column_accessor |
313 | |
314 | This attribute is applied to all methods dynamically installed as a result of |
315 | invoking L<add_columns|DBIx::Class::ResultSource/add_columns>. |
316 | |
317 | =head3 DBIC_method_is_inflated_column_accessor |
318 | |
319 | This attribute is applied to all methods dynamically installed as a result of |
320 | invoking L<inflate_column|DBIx::Class::InflateColumn/inflate_column>. |
321 | |
322 | =head3 DBIC_method_is_filtered_column_accessor |
323 | |
324 | This attribute is applied to all methods dynamically installed as a result of |
325 | invoking L<filter_column|DBIx::Class::FilterColumn/filter_column>. |
326 | |
327 | =head3 DBIC_method_is_*column_extra_accessor |
328 | |
329 | For historical reasons any L<Class::Accessor::Grouped> accessor is generated |
330 | twice as C<{name}> and C<_{name}_accessor>. The second method is marked with |
331 | C<DBIC_method_is_*column_extra_accessor> correspondingly. |
332 | |
333 | =head3 DBIC_method_is_single_relationship_accessor |
334 | |
335 | This attribute is applied to all methods dynamically installed as a result of |
336 | invoking L<might_have|DBIx::Class::Relationship/might_have>, |
337 | L<has_one|DBIx::Class::Relationship/has_one> or |
338 | L<belongs_to|DBIx::Class::Relationship/belongs_to> (though for C<belongs_to> |
339 | see L<...filter_rel...|/DBIC_method_is_filter_relationship_accessor> below. |
340 | |
341 | =head3 DBIC_method_is_multi_relationship_accessor |
342 | |
343 | This attribute is applied to the main method dynamically installed as a result |
344 | of invoking L<has_many|DBIx::Class::Relationship/has_many>. |
345 | |
346 | =head3 DBIC_method_is_multi_relationship_extra_accessor |
347 | |
348 | This attribute is applied to the two extra methods dynamically installed as a |
349 | result of invoking L<has_many|DBIx::Class::Relationship/has_many>: |
350 | C<$relname_rs> and C<add_to_$relname>. |
351 | |
352 | =head3 DBIC_method_is_filter_relationship_accessor |
353 | |
354 | This attribute is applied to (legacy) methods dynamically installed as a |
355 | result of invoking L<belongs_to|DBIx::Class::Relationship/belongs_to> with an |
356 | already-existing identically named column. The method is internally |
357 | implemented as an L<inflated_column|/DBIC_method_is_inflated_column_accessor> |
358 | and is labeled with both atributes at the same time. |
359 | |
360 | =head3 DBIC_method_is_filter_relationship_extra_accessor |
361 | |
362 | Same as L</DBIC_method_is_*column_extra_accessor>. |
363 | |
364 | =head3 DBIC_method_is_proxy_to_relationship |
365 | |
366 | This attribute is applied to methods dynamically installed as a result of |
367 | providing L<the proxied relationship |
368 | attribute|DBIx::Class::Relationship::Base/proxy>. |
369 | |
370 | =head3 DBIC_method_is_m2m_sugar |
371 | |
372 | =head3 DBIC_method_is_m2m_sugar_with_attrs |
373 | |
374 | One of the above attributes is applied to the main method dynamically |
375 | installed as a result of invoking |
376 | L<many_to_many|DBIx::Class::Relationship/many_to_many>. The C<_with_atrs> suffix |
377 | serves to indicate whether the user supplied any C<\%attrs> to the |
378 | C<many_to_many> call. There is deliberately no mechanism to retrieve the actual |
379 | supplied values: if you really need this functionality you would need to rely on |
380 | L<DBIx::Class::IntrospectableM2M>. |
381 | |
382 | =head3 DBIC_method_is_extra_m2m_sugar |
383 | |
384 | =head3 DBIC_method_is_extra_m2m_sugar_with_attrs |
385 | |
386 | One of the above attributes is applied to the extra B<four> methods dynamically |
387 | installed as a result of invoking |
388 | L<many_to_many|DBIx::Class::Relationship/many_to_many>: C<$m2m_rs>, C<add_to_$m2m>, |
389 | C<remove_from_$m2m> and C<set_$m2m>. |
390 | |
5ab72593 |
391 | =head1 METHODS |
392 | |
393 | =head2 MODIFY_CODE_ATTRIBUTES |
394 | |
395 | See L<attributes/MODIFY_type_ATTRIBUTES>. |
396 | |
397 | =head2 FETCH_CODE_ATTRIBUTES |
398 | |
399 | See L<attributes/FETCH_type_ATTRIBUTES>. Always returns the combination of |
400 | all attributes: both the free-form strings registered via the |
401 | L<legacy system|/_attr_cache> and the DBIC-specific ones. |
402 | |
403 | =head2 VALID_DBIC_CODE_ATTRIBUTE |
404 | |
405 | =over |
406 | |
407 | =item Arguments: $attribute_string |
408 | |
409 | =item Return Value: ( true| false ) |
410 | |
411 | =back |
412 | |
413 | This method is invoked when processing each DBIC-specific attribute (the ones |
414 | starting with C<DBIC_>). An attribute is considered invalid and an exception |
415 | is thrown unless this method returns a C<truthy> value. |
416 | |
417 | =head2 _attr_cache |
418 | |
419 | =over |
420 | |
421 | =item Arguments: none |
422 | |
423 | =item Return Value: B<purposefully undocumented> |
424 | |
425 | =back |
426 | |
427 | The legacy method of retrieving attributes declared on DBIC methods |
428 | (L</FETCH_CODE_ATTRIBUTES> was not defined until mid-2016). This method |
429 | B<does not return any DBIC-specific attributes>, and is kept for backwards |
430 | compatibility only. |
431 | |
432 | In order to query the attributes of a particular method use |
433 | L<attributes::get()|attributes/get> as shown in the L</SYNOPSIS>. |
434 | |
435 | =head1 FURTHER QUESTIONS? |
436 | |
437 | Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>. |
438 | |
439 | =head1 COPYRIGHT AND LICENSE |
440 | |
441 | This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE> |
442 | by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can |
443 | redistribute it and/or modify it under the same terms as the |
444 | L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>. |