X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FMethodAttributes.pm;h=9bf5a2d7a98d0fbcb9cc9e62bfb0fe374a7458f4;hb=dc7d89911b7bb98c30208cf73af522a99998dcd6;hp=6dac252815a1b42279dab3db7713a3d14a8f3db7;hpb=5f48fa565dc31b9d22762488afdec8502b8ca515;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/MethodAttributes.pm b/lib/DBIx/Class/MethodAttributes.pm index 6dac252..9bf5a2d 100644 --- a/lib/DBIx/Class/MethodAttributes.pm +++ b/lib/DBIx/Class/MethodAttributes.pm @@ -1,5 +1,4 @@ -package # hide from PAUSE - DBIx::Class::MethodAttributes; +package DBIx::Class::MethodAttributes; use strict; use warnings; @@ -7,13 +6,13 @@ use warnings; use DBIx::Class::_Util qw( uniq refdesc visit_namespaces ); use Scalar::Util qw( weaken refaddr ); -use mro 'c3'; use namespace::clean; -my $attr_cref_registry; +my ( $attr_cref_registry, $attr_cache_active ); sub DBIx::Class::__Attr_iThreads_handler__::CLONE { # This is disgusting, but the best we can do without even more surgery + # Note the if() at the end - we do not run this crap if we can help it visit_namespaces( action => sub { my $pkg = shift; @@ -34,7 +33,7 @@ sub DBIx::Class::__Attr_iThreads_handler__::CLONE { } return 1; - }); + }) if $attr_cache_active; # renumber the cref registry itself %$attr_cref_registry = map { @@ -48,9 +47,16 @@ sub DBIx::Class::__Attr_iThreads_handler__::CLONE { } sub MODIFY_CODE_ATTRIBUTES { - my ($class,$code,@attrs) = @_; - $class->mk_classaccessor('__attr_cache' => {}) - unless $class->can('__attr_cache'); + my $class = shift; + my $code = shift; + + my $attrs; + $attrs->{ + $_ =~ /^[a-z]+$/ ? 'builtin' + : $_ =~ /^DBIC_/ ? 'dbic' + : 'misc' + }{$_}++ for @_; + # compaction step defined $attr_cref_registry->{$_}{weakref} or delete $attr_cref_registry->{$_} @@ -69,10 +75,49 @@ sub MODIFY_CODE_ATTRIBUTES { weaken( $attr_cref_registry->{$code}{weakref} = $code ) } - $class->__attr_cache->{$code} = [ sort( uniq( - @{ $class->__attr_cache->{$code} || [] }, - @attrs, - ))]; + + # increment the pkg gen, this ensures the sanity checkers will re-evaluate + # this class when/if the time comes + mro::method_changed_in($class) if ( + ! DBIx::Class::_ENV_::OLD_MRO + and + ( $attrs->{dbic} or $attrs->{misc} ) + ); + + + # handle legacy attrs + if( $attrs->{misc} ) { + + # if the user never tickles this - we won't have to do a gross + # symtable scan in the ithread handler above, so: + # + # User - please don't tickle this + $attr_cache_active = 1; + + $class->mk_classaccessor('__attr_cache' => {}) + unless $class->can('__attr_cache'); + + $class->__attr_cache->{$code} = [ sort( uniq( + @{ $class->__attr_cache->{$code} || [] }, + keys %{ $attrs->{misc} }, + ))]; + } + + + # handle DBIC_* attrs + if( $attrs->{dbic} ) { + my $slot = $attr_cref_registry->{$code}; + + $slot->{attrs} = [ uniq + @{ $slot->{attrs} || [] }, + grep { + $class->VALID_DBIC_CODE_ATTRIBUTE($_) + or + Carp::confess( "DBIC-specific attribute '$_' did not pass validation by $class->VALID_DBIC_CODE_ATTRIBUTE() as described in DBIx::Class::MethodAttributes" ) + } keys %{$attrs->{dbic}}, + ]; + } + # FIXME - DBIC essentially gobbles up any attribute it can lay its hands on: # decidedly not cool @@ -85,12 +130,61 @@ sub MODIFY_CODE_ATTRIBUTES { # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L29 # https://metacpan.org/source/ZIGOROU/DBIx-Class-Service-0.02/t/lib/DBIC/Test/Service/User.pm#L36 # - return (); + # For the time being reuse the old logic for any attribute we do not have + # explicit plans for (i.e. stuff that is neither reserved, nor DBIC-internal) + # + # Pass the "builtin attrs" onwards, as the DBIC internals can't possibly handle them + return sort keys %{ $attrs->{builtin} || {} }; +} + +# Address the above FIXME halfway - if something (e.g. DBIC::Helpers) wants to +# add extra attributes - it needs to override this in its base class to allow +# for 'return 1' on the newly defined attributes +sub VALID_DBIC_CODE_ATTRIBUTE { + #my ($class, $attr) = @_; + +### +### !!! IMPORTANT !!! +### +### *DO NOT* yield to the temptation of using free-form-argument attributes. +### The technique was proven instrumental in Catalyst a decade ago, and +### was more recently revived in Sub::Attributes. Yet, while on the surface +### they seem immensely useful, per-attribute argument lists are in fact an +### architectural dead end. +### +### In other words: you are *very strongly urged* to ensure the regex below +### does not allow anything beyond qr/^ DBIC_method_is_ [A-Z_a-z0-9]+ $/x +### + + $_[1] =~ /^ DBIC_method_is_ (?: + indirect_sugar + | + (?: bypassable | mandatory ) _resultsource_proxy + | + generated_from_resultsource_metadata + | + (?: inflated_ | filtered_ )? column_ (?: extra_)? accessor + | + single_relationship_accessor + | + (?: multi | filter ) _relationship_ (?: extra_ )? accessor + | + proxy_to_relationship + | + m2m_ (?: extra_)? sugar (?:_with_attrs)? + ) $/x; } sub FETCH_CODE_ATTRIBUTES { - my ($class,$code) = @_; - @{ $class->_attr_cache->{$code} || [] } + #my ($class,$code) = @_; + + sort( + @{ $_[0]->_attr_cache->{$_[1]} || [] }, + ( defined( $attr_cref_registry->{$_[1]}{ weakref } ) + ? @{ $attr_cref_registry->{$_[1]}{attrs} || [] } + : () + ), + ) } sub _attr_cache { @@ -102,3 +196,249 @@ sub _attr_cache { } 1; + +__END__ + +=head1 NAME + +DBIx::Class::MethodAttributes - DBIC-specific handling of CODE attributes + +=head1 SYNOPSIS + + my @attrlist = attributes::get( \&My::App::Schema::Result::some_method ) + +=head1 DESCRIPTION + +This class provides the L inheritance chain with the bits +necessary for L support on methods. + +Historically DBIC has accepted any string as a C attribute and made +such strings available via the semi-private L method. This +was used for e.g. the long-deprecated L, +but also has evidence of use on both C and C. + +Starting mid-2016 DBIC treats any method attribute starting with C +as an I for various DBIC-related methods. +Unlike the general attribute naming policy, strict whitelisting is imposed +on attribute names starting with C as described in +L below. + +=head2 DBIC-specific method attributes + +The following method attributes are currently recognized under the C +prefix: + +=head3 DBIC_method_is_indirect_sugar + +The presence of this attribute indicates a helper "sugar" method. Overriding +such methods in your subclasses will be of limited success at best, as DBIC +itself and various plugins are much more likely to invoke alternative direct +call paths, bypassing your override entirely. Good examples of this are +L and L. + +See also the check +L. + +=head3 DBIC_method_is_mandatory_resultsource_proxy + +=head3 DBIC_method_is_bypassable_resultsource_proxy + +The presence of one of these attributes on a L indicates +how DBIC will behave when someone calls e.g.: + + $some_result->result_source->add_columns(...) + +as opposed to the conventional + + SomeResultClass->add_columns(...) + +This distinction becomes important when someone declares a sub named after +one of the (currently 22) methods proxied from a +L to +L. While there are obviously no +problems when these methods are called at compile time, there is a lot of +ambiguity whether an override of something like +L will be respected by +DBIC and various plugins during runtime operations. + +It must be noted that there is a reason for this weird situation: during the +original design of DBIC the "ResultSourceProxy" system was established in +order to allow easy transition from Class::DBI. Unfortunately it was not +well abstracted away: it is rather difficult to use a custom ResultSource +subclass. The expansion of the DBIC project never addressed this properly +in the years since. As a result when one wishes to override a part of the +ResultSource functionality, the overwhelming practice is to hook a method +in a Result class and "hope for the best". + +The subtle changes of various internal call-chains in C make +this silent uncertainty untenable. As a solution any such override will now +issue a descriptive warning that it has been bypassed during a +C<< $rsrc->overridden_function >> invocation. A user B determine how +each individual override must behave in this situation, and tag it with one +of the above two attributes. + +Naturally any override marked with C<..._bypassable_resultsource_proxy> will +behave like it did before: it will be silently ignored. This is the attribute +you want to set if your code appears to work fine, and you do not wish to +receive the warning anymore (though you are strongly encouraged to understand +the other option). + +However overrides marked with C<..._mandatory_resultsource_proxy> will always +be reinvoked by DBIC itself, so that any call of the form: + + $some_result->result_source->columns_info(...) + +will be transformed into: + + $some_result->result_source->result_class->columns_info(...) + +with the rest of the callchain flowing out of that (provided the override did +invoke L where appropriate) + +=head3 DBIC_method_is_generated_from_resultsource_metadata + +This attribute is applied to all methods dynamically installed after various +invocations of L. Notably +this includes L, +L, +L +and the various L, +B the L (given its +effects are never reflected as C). + +=head3 DBIC_method_is_column_accessor + +This attribute is applied to all methods dynamically installed as a result of +invoking L. + +=head3 DBIC_method_is_inflated_column_accessor + +This attribute is applied to all methods dynamically installed as a result of +invoking L. + +=head3 DBIC_method_is_filtered_column_accessor + +This attribute is applied to all methods dynamically installed as a result of +invoking L. + +=head3 DBIC_method_is_*column_extra_accessor + +For historical reasons any L accessor is generated +twice as C<{name}> and C<_{name}_accessor>. The second method is marked with +C correspondingly. + +=head3 DBIC_method_is_single_relationship_accessor + +This attribute is applied to all methods dynamically installed as a result of +invoking L, +L or +L (though for C +see L<...filter_rel...|/DBIC_method_is_filter_relationship_accessor> below. + +=head3 DBIC_method_is_multi_relationship_accessor + +This attribute is applied to the main method dynamically installed as a result +of invoking L. + +=head3 DBIC_method_is_multi_relationship_extra_accessor + +This attribute is applied to the two extra methods dynamically installed as a +result of invoking L: +C<$relname_rs> and C. + +=head3 DBIC_method_is_filter_relationship_accessor + +This attribute is applied to (legacy) methods dynamically installed as a +result of invoking L with an +already-existing identically named column. The method is internally +implemented as an L +and is labeled with both atributes at the same time. + +=head3 DBIC_method_is_filter_relationship_extra_accessor + +Same as L. + +=head3 DBIC_method_is_proxy_to_relationship + +This attribute is applied to methods dynamically installed as a result of +providing L. + +=head3 DBIC_method_is_m2m_sugar + +=head3 DBIC_method_is_m2m_sugar_with_attrs + +One of the above attributes is applied to the main method dynamically +installed as a result of invoking +L. The C<_with_atrs> suffix +serves to indicate whether the user supplied any C<\%attrs> to the +C call. There is deliberately no mechanism to retrieve the actual +supplied values: if you really need this functionality you would need to rely on +L. + +=head3 DBIC_method_is_extra_m2m_sugar + +=head3 DBIC_method_is_extra_m2m_sugar_with_attrs + +One of the above attributes is applied to the extra B methods dynamically +installed as a result of invoking +L: C<$m2m_rs>, C, +C and C. + +=head1 METHODS + +=head2 MODIFY_CODE_ATTRIBUTES + +See L. + +=head2 FETCH_CODE_ATTRIBUTES + +See L. Always returns the combination of +all attributes: both the free-form strings registered via the +L and the DBIC-specific ones. + +=head2 VALID_DBIC_CODE_ATTRIBUTE + +=over + +=item Arguments: $attribute_string + +=item Return Value: ( true| false ) + +=back + +This method is invoked when processing each DBIC-specific attribute (the ones +starting with C). An attribute is considered invalid and an exception +is thrown unless this method returns a C value. + +=head2 _attr_cache + +=over + +=item Arguments: none + +=item Return Value: B + +=back + +The legacy method of retrieving attributes declared on DBIC methods +(L was not defined until mid-2016). This method +B, and is kept for backwards +compatibility only. + +In order to query the attributes of a particular method use +L as shown in the L. + +=head1 FURTHER QUESTIONS? + +Check the list of L. + +=head1 COPYRIGHT AND LICENSE + +This module is free software L +by the L. You can +redistribute it and/or modify it under the same terms as the +L.