Hi Oliver,

Oliver Tappe schrieb:

> attached is a function I have found useful in some of the OTRS-modules I 
> worked on recently. It is called CheckParams() and its purpose is to offer an 
> intuitive and flexible way of checking the params-hash that has been passed 
> into a method.
> 
> Currently, many methods contain code like this:
> 
>     # check all needed objects
>     foreach (qw(ConfigObject LogObject DBObject TimeObject MainObject)) {
>         die "Got no $_" if (!$Self->{$_});
>     }
> 
> plus more checks for additional parameters that must be passed in via 
> the %Params hash.
> 
> When using CheckParams, the code looks like this:
> 
>     # check all needed objects
>     CheckParams($Self, sub {
>         'ConfigObject'   => '!Class=Kernel::Config',
>         'DBObject'       => '!Class=Kernel::System::DB',
>         'LogObject'      => '!Class=Kernel::System::Log',
>         'MainObject'     => '!Class=Kernel::System::Main',
>         'TimeObject'     => '!Class=Kernel::System::Time',
>     });
> 
> As you can see, you pass CheckParam() the hash-ref that shall be checked and 
> a 
> specification (for performance reasons this should always be an anonymous 
> subroutine). In the specification, you simply declare the supported 
> parameters, whether or not they are required (!) or optional (?) or if they 
> have to be of a specific class (Class=...).
> 
> CheckParams() supports checking of more complex data structures, too:
> 
>     CheckParams($Params, sub {
>         'GeneralAttrs' => {
>             'From'              => '!',
>             'To'                => '!',
>         },
>         'JobAttrs'  => {
>             'Creator'           => '!',
>             'Changes' => [
>                 {
>                     'Action'    => 'm{^(create|change|delete|sync)$}',
>                     'Path'      => '!',
>                     'Content'   => '?',
>                 }
>             ],
>         }
>     });
> 
> This code snippet makes sure that the given $Params-hash contains two 
> elements 
> (GeneralAttrs & JobAttrs), each of which has to follow the specified 
> substructures.
> 
> As I believe this function could be useful throughout OTRS (now that we have 
> the 2.2-branch), I'd like to add it to the framework. I can see two ways of 
> doing that:
> 
> 1.) non-OO style: add some kind of helper module (e.g. Utils.pm) and add 
> CheckParams() as a *function* to that.
> 2.) OO style: add a new module Utils/Params.pm which contains a 
> *class-method* 
> Check(), such that you'd have to invoke it like this
>     Utils::Params->Check($Params, sub {
>       ...
>     });
> 
> So what do you think? Do you like the idea in general at all? If so, how 
> should that functionality be integrated (OO- or non-OO-style)?
> 
> Please fire any suggestions or questions to this list or directly my way.


-=> Yes, 'die "Got no $_" if (!$Self->{$_});' the die() is not a option. ;)

Bit I see different things to integrate it into the current OTRS
framework (may be we are talking about two different things?).

Anyway, here my points:

o) Keep it clean:
If we have a check method in the framework, we should replace any
other checks from the whole framework with the new check to keep it clean.

o) Check it every time?/Performance Issue:
"# check all needed objects" - For normal framework, it should not be
necessary to check the default objects. The framework should only
deliver existing objects. You never should use e. g. $Self->{LogObject}
for other perl objects. I also would see a really performance issue if
we would check the object content every time. -=> So for the OTRS
framework I would expect to not need to check the base objects.

o) OO-Style;
Only OO-Style. It would be not good to switch to other styles.

o) confess():
I have looked into your source code. I never have seen confess() in
the OTRS framework bevor. Whats about this?

o) Filename:
Utils/Params.pm is not on the current directory style.
Kernel/System/Params.pm or to extend the existing
Kernel/System/CheckItem.pm would sounds a little bit better to me.

o) Utils::Params->Check($Params, sub { ... }); looks a little bit
strange to me. I would suggest something like "$Self->{ParamsObject}"
oder "$Self->{CheckObject}". And a methode like
"$Self->{ParamsObject}->Check(
    'ConfigObject'   => '!Class=Kernel::Config',
    'DBObject'       => '!Class=Kernel::System::DB',
    'LogObject'      => '!Class=Kernel::System::Log',
    'MainObject'     => '!Class=Kernel::System::Main',
    'TimeObject'     => '!Class=Kernel::System::Time',
);"

o) One other thing would be to define more via attributes. E. g.

    'TimeObject'     => '!Class=Kernel::System::Time',

  Is IMO not so good because you need to use some cpu time to use
  a regexp for to find out what "!Class=" is.

  I mean something like e. g.

    'TimeObject'     => {
        Operation => '!',
        Type => 'Class',
        Match => 'Kernel::System::Time',
    },

  IMO this would be faster and need less cpu time.

o Maybe we need something like Kernel::System::Main->Die().

So back to the two different things:
IMO we need only a exists-check for objects (not content related) but we
could use a new Check() for param validation of e. g. strings or other
content.

What do you think? :-)

Greetings,

 -Martin

> ------------------------------------------------------------------------
> 
> =item CheckParams()
> 
> Utility function that can be used by any method that accepts param-hashes
> to check if the given parameters actually match the expectations.
> 
> Each individual parameter has a specification that describes the expectation
> that the calling function has towards this param. The following specifications
> are supported:
> 
> * '!'          - the parameter is required
> * '?'          - the parameter is optional
> * 'm{regex}'   - the parameter must match the given regex
> * '!Class=...' - the parameter is required and must be an object of the given 
> class
> * '?Class=...' - if the parameter has been given, it must be an object of the 
> given class
> 
> The function will confess for any unknown, missing, or non-matching param.
> 
> =cut
> 
> sub CheckParams
> {
>     my $Params     = shift or confess('need to pass in params-hashref!');
>     my $ParamsSpec = shift or confess('need to pass in params-spec-hashref 
> (or -sub)!');
> 
>     # TODO: allow to switch off this function via configuration in production
>     #       environments, as it is rather heavy
> 
>     # fetch param-spec from function, if that has been given:
>     if (ref($ParamsSpec) eq 'CODE') {
>         $ParamsSpec = $ParamsSpec->();
>     }
> 
>     # print a warning for any unknown parameters that have been given:
>     my @UnknownParams
>         =   grep { !exists $ParamsSpec->{$_}; }
>             keys %$Params;
>     if (@UnknownParams) {
>         my $UnknownParamsStr = join ',', @UnknownParams;
>         confess("Enocuntered unknown params: '$UnknownParamsStr'!\n");
>     }
> 
>     # check if all required params have been specified:
>     foreach my $Param (keys %$ParamsSpec) {
>         my $Spec = $ParamsSpec->{$Param};
>         if (ref($Spec) eq 'HASH') {
>             # Handle nested specs by recursion:
>             my $SubParams = $Params->{$Param};
>             if (!defined $SubParams) {
>                 confess("Required param '$Param' is missing!");
>             }
>             CheckParams($SubParams, $Spec);
>         }
>         elsif (ref($Spec) eq 'ARRAY') {
>             # Handle nested spec arrays by looped recursion:
>             my $SubParams = $Params->{$Param};
>             if (!defined $SubParams) {
>                 confess("Required param '$Param' is missing!");
>             }
>             elsif (ref($SubParams) ne 'ARRAY') {
>                 confess("Value for param '$Param' must be an array-ref!");
>             }
>             foreach my $SubParam (@$SubParams) {
>                 CheckParams($SubParam, $Spec->[0]);
>             }
>         }
>         elsif ($Spec eq '!') {
>             # required parameter:
>             if (!exists $Params->{$Param}) {
>                 confess("Required param '$Param' is missing!");
>             }
>         }
>         elsif ($Spec =~ m{^\!Class=(.+)$}i) {
>             my $Class = $1;
>             # required parameter ...
>             if (!exists $Params->{$Param}) {
>                 confess("Required param '$Param' is missing!");
>             }
>             # ... of specific class
>             if (!$Params->{$Param}->isa($Class)) {
>                 confess("Param '$Param' is not a '$Class', but that is 
> required!");
>             }
>         }
>         elsif ($Spec eq '?') {
>             # optional parameter - nothing to do
>         }
>         elsif ($Spec =~ m{^\?Class=(.+)$}i) {
>             my $Class = $1;
>             # optional parameter ...
>             if (exists $Params->{$Param}) {
>                 # ... of specific class
>                 if (!$Params->{$Param}->isa($Class)) {
>                     confess("Param '$Param' is not a '$Class', but that is 
> required!");
>                 }
>             }
>         }
>         elsif ($Spec =~ m{^m{(.+)}$}) {
>             # try to match given regex:
>             my $Regex = $1;
>             my $Value = $Params->{$Param};
>             if ($Value !~ m{$Regex}) {
>                 confess("Required param '$Param' isn't matching regex 
> '$Regex' (given value was '$Value')!");
>             }
>         }
>         else {
>             # complain about unknown spec:
>             confess("Unknown param-spec '$Spec' encountered!");
>         }
>     }
> 
>     return scalar 1;
> }

_______________________________________________
OTRS mailing list: dev - Webpage: http://otrs.org/
Archive: http://lists.otrs.org/pipermail/dev
To unsubscribe: http://lists.otrs.org/cgi-bin/listinfo/dev

Reply via email to