Skip to content

Commit

Permalink
Item13897: First steps on OO redesign task.
Browse files Browse the repository at this point in the history
Changes are mostly about to probe the direction.
Concepts are inlined with TML self-doc.
  • Loading branch information
vrurg committed Dec 29, 2015
1 parent d81582b commit 87cd588
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 119 deletions.
13 changes: 10 additions & 3 deletions core/lib/Foswiki.pm
Expand Up @@ -45,7 +45,7 @@ use strict;
use warnings;
use Assert;
use Cwd qw( abs_path );
use Error qw( :try );
use Try::Tiny;
use File::Spec ();
use Monitor ();
use CGI (); # Always required to get html generation tags;
Expand Down Expand Up @@ -98,6 +98,11 @@ our $CC = "\0-->";
# corrupting data spaces.
our $inUnitTestMode = 0;

use Moo;
use namespace::clean;

extends 'Foswiki::Object';

sub SINGLE_SINGLETONS { 0 }
sub SINGLE_SINGLETONS_TRACE { 0 }

Expand Down Expand Up @@ -1110,7 +1115,9 @@ sub _isRedirectSafe {
my $redirect = shift;

return 1 if ( $Foswiki::cfg{AllowRedirectUrl} );
return 1 if $redirect =~ m#^/#; # relative URL - OK

# relative URL - OK
return 1 if $redirect =~ m#^/#;

#TODO: this should really use URI
# Compare protocol, host name and port number
Expand Down Expand Up @@ -1402,7 +1409,7 @@ sub isValidTopicName {
try {
Foswiki::Store::encode( $name, 1 );
}
catch Error with {
catch {
$badName = 1;
};
return 0 if $badName;
Expand Down
119 changes: 119 additions & 0 deletions core/lib/Foswiki/Exception.pm
@@ -0,0 +1,119 @@
# See bottom of file for license and copyright information

=begin TML
---+ package Foswiki::Exception
Base class for all Foswiki exceptions. This is still a concept only.
Basic principles behind exceptions:
1. Exceptions are using =CPAN:Try::Tiny=. Use of =CPAN:Error= module is no longer
recommended.
1. Exception classes are inheriting from =Foswiki::Exception=.
1. =Foswiki::Exception= is an integral part of Fowiki's OO system and inheriting from =Foswiki::Object=.
1. =Foswiki::Exception= is utilizing =Throwable= role. Requires this module to be installed.
1. Exception classes inheritance shall form a tree of relationships for fine-grained error hadling.
The latter item might be illustrated with the following expample (for inherited classes =Foswiki::Exception= prefix is skipped for simplicity though it is recommended for code readability):
* Foswiki::Exception
* Core
* Engine
* CGI
* Rendering
* UI
* Validation
* Oops
* Fatal
This example is not proposed for implementation as hierarchy is exceptions has to be thought out based on many factors.
It would be reasonable to consider splitting Oops exception into a fatal and non-fatal variants, for example.
---++ Notes on Try::Tiny
Unlike =CPAN:Error=, =CPAN:Try::Tiny= doesn't support catching of exceptions based on
their respective classes. It has to be done manually.
Alternatively =CPAN:Try::Tiny::ByClass= might be considered. It adds one more dependency
of =CPAN:Dispatch::Class= module.
One more alternative is =CPAN:TryCatch= but it is not found neither in MacPorts,
nor in Ubuntu 15.10 repository, nor in CentOS. Though it is a part of FreeBSD ports tree.
=cut

package Foswiki::Exception;
use Moo;
use namespace::clean;

extends qw(Foswiki::Object);

with 'Throwable';

BEGIN {
if ( $Foswiki::cfg{UseLocale} ) {
require locale;
import locale();
}
}

has line => ( is => 'rw' );
has file => ( is => 'rw' );

package Foswiki::Exception::Engine;
use Moo;
our @_newParameters = qw(status reason response);
use namespace::clean;
extends qw(Foswiki::Exception);

has status => ( is => 'ro' );
has reason => ( is => 'ro' );
has response => ( is => 'ro' );

=begin TML
---++ ObjectMethod stringify() -> $string
Generate a summary string. This is mainly for debugging.
=cut

sub stringify {
my $this = shift;
return
qq(EngineException: Status code "$this->{status}" defined because of "$this->{reason}".);
}

1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Additional copyrights apply to some or all of the code in this
file as follows:
Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org
and TWiki Contributors. All Rights Reserved. TWiki Contributors
are listed in the AUTHORS file in the root of this distribution.
Copyright (C) 2005 Martin at Cleaver.org
Copyright (C) 2005-2007 TWiki Contributors
and also based/inspired on Catalyst framework, whose Author is
Sebastian Riedel. Refer to
http://search.cpan.org/~mramberg/Catalyst-Runtime-5.7010/lib/Catalyst.pm
for more credit and liscence details.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version. For
more details read LICENSE in the root of this distribution.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
As per the GPL, removal of this notice is prohibited.
171 changes: 55 additions & 116 deletions core/lib/Foswiki/Object.pm
Expand Up @@ -13,141 +13,80 @@ behaviour and general policies for all descendants.
---++ Behavior
=Foswiki::Object= main goals are to create and destroy objects properly and
to unify some very basic object interfaces like property management, for
instance.
=Foswiki::Object= is a subclass of Moo and as such inherits all it's
features.
---+++ Syntax sugar
Unlike any OO toolkit =Foswiki::Object= is not introducing special
constructs only to make it look good and behave like "in that other
language". It's not a big deal to stick to ye olde good Perl 5 way. Though
if something would make life easier without big performance penalty then
why resisting to implement it?
=cut

This is why after considering that:
use strict;
use warnings;
use Assert;
use Moo;
use namespace::clean;

a. to be correctly managed and cleaned properties have to be somehow
registered
a. they have never be accessed directly but by calling a correspoding
object method only
=begin TML
it was decided that implementing a semi-keyword =has= would be rather for
good than for bad. No typification, no other hidden magic staff. Simple
support for read/write modificator and default value:
---++ ClassMethod BUILDARGS()
<verbatim>
package Foswiki;
use Foswiki::Object;
our @ISA=qw( Foswiki::Object );
has session => 'ro';
has somePropery => rw => 'Default';
</verbatim>
Converts positional constructor parameters to named ones. Tries to detect if constructor is already being called using named notation.
Possible extension by means of additional key/value attribute pairs is
considered. Could be used to define a validator. For example:
The =BUILDARGS()= uses array =@_newParameters= declared statically on a class to get information about the order of parameters.
For example, for =Foswiki::Class=:
<verbatim>
has wikiName => rw => 'DefaultUser', validate => \&_validateWikiName;
has count => rw => 0, validate => qr/^\d+$/;
</verbatim>
What =has= does is it generates 2 or 3 object methods:
* public method =someProperty()=
* private methods =_getSomeProperty()= and =_setSomeProperty()= unless
they're predefined by class module.
* records information about the property in =%Class::_CLASS= hash.
By predefining the =_get= and/or =_set= methods a class can have full
control over a property management and even create fully virtual
properties.
Overriding of the public method is considered senseless and is not
recommended though not prohibited either.
---+++ Initialization
None of deriving classes shall have method =new()=. It's the prerogative of
=Foswiki::Object= to have it. There is no reason to override it. Seriously.
If there is one to do so – think twice and find a way to get around. If no
ideas come out then something has to be changed in =Foswiki::Object= code.
What =new()= does is:
package Foswiki::Class;
use Moo;
* blessing a hash in object class
* preparing object parameters
* initializing the new object with these parameters
our @_newParameters = qw( param1 param2 );
use namespace::clean;
---++++ Validating and adjusting object parameters.
has param1 => (is => 'rw');
has param2 => (is => 'ro');
has param3 => (is => 'rw');
Upon blessing a new-born object the =new()= method initiates a process of
sanitizing of parameters passed to the object before they actually end up
in corresponding properties, or influence the object initialization
process, or in any other way have impact over the object's life.
As a part of unification efforts the [[#InitMethod][=_init()=]] object
method receives it's parameters in =key => value= pairs. But for backward
compatibility and for other developers convenience some of =new()= methods
are allowed to get their parameters in positional form. Mapping of latter
into former is done by =_mapObjectParameters()= method or automatically by
=Foswiki::Object= itself if the list of parameter names has been passed
over in =use= arguments:
<verbatim>
use Foswiki::Object qw( postionalParameter1 positionalParameter2 theLastOne );
1;
</verbatim>
The resulting parameters hash is been passed over to
=_checkObjectParameters()= method which in turn must do preliminary
validation of parameter values and set those missing to their default
values. Presetting to defaults would mostly be done by =Foswiki::Object=
itself and shall not be of descendant business unless there is some
specific about a particular parameter. Yet, if the property validators are
to be considered as the way to go then =Foswiki::Object= might take care of
this task too.
---++++ Initializing object
The resulting hash of parameters then gets passed over to object method
=_init()=. This is where descendant code would do most of its job of
preparing the new object. Though the task of setting properties values
could still be done by =Foswiki::Object= if no special care needs to be
taken about them and calling corresponding =_set= method is enough.
---+++ Destruction
When object finishes it's life cycle =Foswiki::Object= takes care of
cleaning it up. First of all, =_finish()= or =finish()= methods are called.
Actually it is uncertain if this method has any value as a public one. More
like it's better be kept private. But this is to be considered. What is
more important is not to forget to call =SUPER::_finish()=.
the following notations are valid:
Then all properties not wiped out by descendat's =_finish()= method are
deleted.
To be considered another keyword which would enumerate additional object
keys/properties to be taken care of by the destructor. For example, it may
look like this:
<verbatim>
cleanup qw( web topic remoteUser context etc );
<verbtaim>
my $object1 = Foswiki::Class->new($param1, $param2);
my $object2 = Foswiki::Class->new($param2);
my $object3 = Foswiki::Class->new(param1 => 1, param2 => '2', param3 => 'additional');
</verbatim>
It won't add much except making it easier to locate and check this list.
---+++ Internal magic notes.
As syntax sugar functionality relies upon =import()= sub being run during
early module load it's better to avoid any use of this sub in a descendant
class module. Instead =Foswiki::Object= would call =_class_import()= sub
for you.
Note that for =$object2= the =BUILD()= method would be called with undefined param2.
=cut

use strict;
use warnings;
use Assert;
sub BUILDARGS {
my ( $class, @params ) = @_;

my $paramHash;

no strict 'refs';
if ( defined *{ $class . '::_newParameters' }{ARRAY} ) {
my @newParameters = @{ $class . '::_newParameters' };
my $isHash = 0;
if ( ( @params % 2 ) == 0 ) {
my $prop_re = '^(' . join( '|', @newParameters ) . ')$';
my %params = @params;
foreach my $prop ( keys %params ) {
last if $isHash = ( $prop =~ $prop_re );
}
}
unless ($isHash) {
@{$paramHash}{@newParameters} = @params;
}
}

# If $paramHash is undef at this point then either @params is a key/value pairs array or no @_newParameters array defined.
$paramHash = {@params} unless defined $paramHash;

use strict 'refs';

return $paramHash;
}

1;
__END__
Expand Down

0 comments on commit 87cd588

Please sign in to comment.