Skip to content

Commit

Permalink
Item14237: Started documentation on basic programming concepts of v3
Browse files Browse the repository at this point in the history
  • Loading branch information
vrurg committed Oct 4, 2017
1 parent 0bab2fc commit 07c6274
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 11 deletions.
8 changes: 7 additions & 1 deletion core/data/System/FoswikiV3Essentials.txt
@@ -1,6 +1,7 @@
%META:TOPICINFO{author="ProjectContributor" date="1506998025" format="1.1" version="1"}%
%META:TOPICINFO{author="ProjectContributor" date="1507086384" format="1.1" version="1"}%
%META:TOPICPARENT{name="DeveloperDocumentationCategory"}%
---+ Foswiki v3 Essentials
%TOC%

This category is dedicated to development under Foswiki v3 core.

Expand Down Expand Up @@ -39,6 +40,11 @@ Basically, what makes v3 different from all previous versions are:
There are some under the hood changes in behaviour of some components like
=%PERLDOC{"Foswiki::Request"}%= initialization procedures; or alike.

---++ Basic concepts

$ [[OOCodeConcepts]] : Main concepts of programming under %WIKITOOLNAME% v3.

---++ New standards

$ [[SpecFileFormat]] : Config Specs v2

214 changes: 214 additions & 0 deletions core/data/System/OOCodeConcepts.txt
@@ -0,0 +1,214 @@
%META:TOPICINFO{author="ProjectContributor" date="1507078114" format="1.1" version="1"}%
%META:TOPICPARENT{name="FoswikiV3Essentials"}%
---+ Programming for Foswiki v3
%TOC%

This topic explains basics requirements and specifics of programming for Foswiki
v3. These include:

* Object oriented techniques
* Application-centric code
* Exception handling

It is also implied that the reader of this topic is already familiar with Perl's
OO programming. It is also required to learn about =CPAN:Moo= before proceeding
any further as %WIKITOOLNAME% is using it as the base OO framework.

---++ Basic code requirements

* Minimal supported Perl version is 5.14 <verbatim>use v5.14;</verbatim>
* =strict= and =warning= pragmas
* Use of =CPAN:namespace::clean= (see below)

---++ Object oriented techniques

The first rule of programming for %WIKITOOLNAME% says: "Nobody talks about..."
Er, sorry, that's from another documentation I've being studying recently. ;)

Ok, let's give it a second try:

: Everything is inheriting from =%PERLDOC{Foswiki::Object}%=.

That's it. You're now ready to go as long as you follow the rule. The rest of
this section is mostly about special features and nuances.

What is so important about inheriting from a single core class? First of all,
it allows for clear separation of Foswiki and non-Foswiki objects by simply
testing

<verbatim>
if $obj->isa("Foswiki::Object")
</verbatim>

Even if you think this is not important to you – still, it may play significant
role for some core or extension code.

Another important feature of =Foswiki::Object= is its support for debugging and
some object manipulation. For example, it supports object cloning - a very handy
feature; especially for testing framework.

---+++ Construction and desctruction

As required by =Moo=, any object is constructed using named parameters unless
some special care is taken by a class and implemented using =BUILDARGS()=
class method. Otherwise there is nothing special about the default constructor
unless the code is working in =DEBUG= mode (see %PERLDOC{Assert}% and
%PERLDOC{Foswiki::Object}%).

Similar note applies to the destruction stage which usually does nothing unless
the =DEBUG= mode is on. In this case a number of last-second checks is performed
to catch simple errors of bypassing =Moo='s attribute accessors and manipulating
directly on object's hash.

%X% It is highly recommended to avoid using class =new()= method and rely upon
%PERLDOC{"Foswiki::App" method="create"}% instead.
%PERLDOC{"Foswiki::ExtManager"}% documentation explains the importance of this
rule.

%X% A newly created object is not guaranteed to exist in fully prepared state
as some attributes might still remain uninitialized after construction is done.
See the [[#Attributes][Attributes]] section and keep in mind *lazy* option.

---+++ Attributes

The attributes within Foswiki code are the standard =CPAN:Moo= attributes.
There is a debugging trick implemented by %PERLDOC{Foswiki::Class}%, but it
still doesn't change the default behavior. But there is a convention with
regard to attributes options and their use:

1 Attributes are named using
[[https://en.wikipedia.org/wiki/Camel_case][camelCase]] style, common across
Foswiki code.
1 Private attribute names must start with underscore: =_aPrivateAttr=.
1 Whenever an attribute is utilizing *lazy* option it must be initialized
with *builder* instead of *default*. The *builder* must define a method
name which starts with _'prepare'_ followed by attribute name with capitalized
first character. If attribute is private the leading underscore char must be
removed and placed in front of _'prepare'_. Here is naming examples:
$ someAttribute : =prepareSomeAttribute=
$ !_aPrivateAttribute : =_prepareAPrivateAttribute=

Use of *builder* option is important for class inheritance where descending
class may want to override initialization implementation of its base class. For
example:

<verbatim class="perl">
package Foswiki::BaseClass;

use Foswiki::Class;
extends qw<Foswiki::Object>

has someAttribute => (
is => 'rw',
lazy => 1,
builder => 'prepareSomeAttribute',
);

sub prepareSomeAttribute {
my $this;

return 1.2;
}

1;
</verbatim>

<verbatim class="perl">
package Foswiki::Descendant;

use Foswiki::Class;
extends qw<Foswiki::BaseClass>;

around prepareSomeAttribute => sub {
my $orig = shift;
my $this = shift;

if ( $someSpecificCondition ) {
# Under certain circumstances the constant might differ from the default
return 1.22;
}

return $orig->($this);
};

1;
</verbatim>

%X% As it is clear from the definition of *lazy* option, attribute remains
uninitialized until referenced. With respect to this fact it is important to
remember that sometimes it is better to use *predicate* option and check if
attribute has been initialized instead of testing if it's _defined_. The latter
may cause unwanted deep recursion if used within *builder* code of another
attribute when the two directly or indirectly depend on each other.

%X% Special care must be taken of attributes storing back references to other
objects. Mostly it is related to situations when a child object is referring its
parent. As this is a typical situation where circular references occur and
cause garbage collection to fail use of *weak_ref* option is highly recommended
to be considered. See %PERLDOC{Foswiki::AppObject}%= role code as an example.

---+++ Avoiding infrastructure code

What would a typical class header look like in Foswiki v3? Lets see how we make
it comply to the base requirements:

<verbatim>
package Foswiki::SomeClass;

use v5.14;
use Moo;
use namespace::clean;
extends qw<Foswiki::Object>;
with qw<Foswiki::AppObject>;
</verbatim>

Though the last line is not mandatory but most of the core classes use it.

So, this is what we would type every time a new class is started. Not only it
takes our priceless time but it may harm our fragile patience. Then, again,
what if we bump the minimal required Perl version?

In order to get around these annoyances Foswiki provides a wrapper for
=CPAN:Moo= which takes care of this burden:

<verbatim>
package Foswiki::SomeClass;

use Foswiki::Class qw<app>;
extends qw<Foswiki::Object>;
</verbatim>

Minus 3 lines, plus +20 to readability.

There are more useful tricks provided by =%PERLDOC{Foswiki::Class}%=. Read the
module's documentation to find out more about them. Here is just a simple
example:

<verbatim>
package Foswiki::SomeClass;

use Foswiki::Class;
extends qw<Foswiki::Object>;

has attr1 => (
is => 'rw',
lazy => 1,
builder => 'prepareAttr1',
);

# This is how it would look previously when prepareAttr1 sub is only a stub
# allowing a descendant to implement the logic:
# sub prepareAttr1 {}

stubMethods qw<prepareAttr1>;
</verbatim>

The example may not look very convincing in its current form with only single
attribute in use. But simply imagine a number of attributes defined this way.

---+++ Related

=%PERLDOC{Foswiki::Object}%=, =%PERLDOC{Foswiki::Class}%=

---++ Application

6 changes: 4 additions & 2 deletions core/lib/Foswiki/Class.pm
Expand Up @@ -11,19 +11,21 @@ use warnings;
---+ Package Foswiki::Class
This is a wrapper package for Moo and intended to be used as a replacement and
a shortcut for a bunch lines of code like:
a shortcut for a bunch of code like:
<verbatim>
use v5.14;
use Moo;
use namespace::clean;
with qw(Foswiki::AppObject);
extends qw<Foswiki::Object>;
with qw<Foswiki::AppObject>;
</verbatim>
The above could be replaced with a single line of:
<verbatim>
use Foswiki::Class qw(app);
extends qw<Foswiki::Object>;
</verbatim>
---++ Usage
Expand Down
4 changes: 2 additions & 2 deletions core/lib/Foswiki/ExtManager.pm
Expand Up @@ -192,8 +192,8 @@ extensions themselves, must comply to the following rules:
* Any class must be a direct or indirect descendant of =Foswiki::Object=.
* New class instances (objects) must be created using
=Foswiki::App::create()= method which is available for classes consuming
=Foswiki::AppObject= role.
=Foswiki::App::create()= method which is directly available as a
ObjectMethod for classes consuming =Foswiki::AppObject= role.
The second rule is redundant for extensions because they're inheriting from
=Foswiki::Extension= which already consumes the role.
Expand Down
21 changes: 15 additions & 6 deletions core/lib/Foswiki/Object.pm
Expand Up @@ -16,7 +16,7 @@ behaviour and general policies for its descendants.
---+++ Behavior changing conditions
This class' behavior could be changed by either a environment variable
=FOSWIKI_NOSTACKTRACE= and =Assert='s module constant =DEBUG=. These changes
=FOSWIKI_NOSTACKTRACE= and =%PERLDOC{"Assert" text="Assert's"}%= module constant =DEBUG=. These changes
should be completely transparent for the rest of core code and only be of any
interest for debugging purposes. The =DEBUG= constant is of main significance
here. =FOSWIKI_NOSTACKTRACE= is taken into account with =DEBUG= being _true_
Expand All @@ -41,12 +41,12 @@ code in =DEBUG= mode might become a part of Buddhist patience training for some.
Since =Moo= doesn't provide any standard checker for an attribute =isa= option
we wrote our own basic validtation methods. Those are static methods which are
named =isaTYPE()=. Currently only three types are supported: ARRAY, HASH, and
CLASS. See respective methods documentation.
named =isaTYPE()=. Currently only three =TYPEs= are supported: *ARRAY*, *HASH*,
and *CLASS*. See respective methods documentation.
A typical use of them would look like the following code:
<verbatim>
<verbatim class="perl">
package Foswiki::SomeClass;
...
has meta => (
Expand All @@ -57,6 +57,15 @@ has meta => (
See =CPAN:Moo= IMPORTED SUBROUTINES -> =has= documentation.
---+++ Object stringifiction
=Foswiki::Object= overrides the stringification operator =""=
(see CPAN:overload) and maps it onto
=%PERLDOC{"Foswiki::Object" method="to_str" text="to_str()"}%= method. The
original method preserves Perl's standard behavior but could be overridden
by inheriting classes to achieve their goals. Check the
%PERLDOC{Foswiki::Exception}% code for an example use of this method.
=cut

require Carp;
Expand Down Expand Up @@ -119,8 +128,8 @@ has __clone_heap =>
---+++ ObjectMethod new( %params ) -> $obj
All Foswiki classes must use named parameters for their =new()= method and must
be created using =Foswiki::App= =create()= method unless it is not possible for
a strong reason.
be created using =%PERLDOC{"Foswiki::App" method="create"}%= method unless it is
not possible for a strong reason.
=cut

Expand Down

0 comments on commit 07c6274

Please sign in to comment.