All components extend the JinnBaseComponent
class or some sub class of it. This provides the framework for the component
to be bound to a module, access services, and access other components bound to
the same module. Continuing with our news example from the introductory
section we might have the following:
class ProjectNewsFeedRetrieve extends JinnBaseComponent
{
//
// Set the default property path.
//
var $propertyPath = 'Project/newsFeed/retrieve';
function ProjectNewsFeed()
{
//
// Constructor stuff.
//
}
function execute()
{
//
// Code to be run after components have been bound to
// a module.
//
}
//
// Any methods pertinent to the component.
//
}
From the above example there are a couple of things to consider. The first is
the instance variable, $propertyPath. This
provides a convenient and powerful feature for components whereby object
properties can be externally defined for a component via the Property System. Properties are very important
and just like components can be interchanged for modules, the property path
can be switched for a component which provides a great deal of flexibility and
separation.
The other thing to consider is the execute()
method. Generally speaking most initialization code is performed within the
constructor. This also is true of components provided that no attempts are
made to access other components in the module. The reason for this is that the
creation of the component occurs before the binding of the component to the
module. However, once the component has been bound, then its execute() method is called. At this stage it is safe to make
requests for other components and to use their methods and member variables.
Given that our example component above has real code in it to perform the real
job of retrieving the news content from some feed. We don't currently do
anything with it. The example above would be an example of a data or logic component. There
is one other type of component -- the render
component. The render component has one major difference from the other
components. The render component may have it's render() method called from within the page by calling the
render() method of the module. This will occur
after the module has been declared and allows for all module initializations
to occur at the top of the document. Given this arrangement all output occurs
after any initialization, data retrieval, or logic has been performed. This
generally works out great for error handling since errors will be detected
before any content is output, and can thus be handled gracefully. An example
of a render component follows:
class ProjectNewsFeedDisplay extends JinnBaseComponent
{
//
// Set the default property path.
//
var $propertyPath = 'Project/newsFeed/display';
function ProjectNewsFeed()
{
//
// Constructor stuff.
//
}
function execute()
{
//
// Code to be run after components have been bound to
// a module.
//
}
function render( $selector=null )
{
//
// Code to run to generate output to the browser.
//
}
//
// Any methods pertinent to the component.
//
}
So far so simple, but what's that $selector
parameter to the render() method? The purpose
of the parameter is to provide a means for accessing parts of the rendered
output. Perhaps the news feed has a title, a copyright or some other
information that can be partitioned. When accessing the component from your
page you can optionally provide a selector to the render method which it can
use to select pieces of the output.
Now that we have two components it is a good time to demonstrate where the module aspect fits in. As stated earlier, a module is really a description of how components are bound together. The following example illustrates our two components being bound together in a module declaration:
<script language="php">
$newsFeedModule = $dl->do->load( array
(
'logic' => array
(
array
(
'name' => 'newsFeed',
'source' => '//Project/newsFeed/retrieve.inc',
'properties' => array
(
//
// Overriden properties.
//
array
(
'group' => 'urls',
'name' => 'sourceUrl',
'value' => 'http://www.somegreatnews.cmx',
),
),
),
),
'render' => array
(
array
(
'source' => '//Project/newsFeed/display.inc',
),
),
) );
</script>
The above example binds an instance of the ProjectNewsFeedRetrieve class together with an instance of the
ProjectNewsFeedDisplay class. From the
declaration we see that a module is described by a series of arrays with each
array denoting a component of the desired module. This looks somewhat heavy to
work with and to some degree it is; however, if you are using InterJinn in
conjunction with the built in TemplateJinn system then you would
be able to use the following to describe the module:
<jinn:module name="newsFeed" noRender="true">
<jinn:component
type="logic"
name="newsFeed"
source="//Project/newsFeed/retrieve.inc"
/>
<jinn:component
type="render"
source="//Project/newsFeed/display.inc"
/>
</jinn:module>
You will find that field names (the keys) of the PHP code declaration style
are identical to the attributes defined for the <jinn:module/> tag when using the TemplateJinn system.
The first thing probably on your mind is "What the heck is
$dl?". This is one of the few
core objects that the InterJinn engine loads by default. $dl is short for Dynamic Loader. As you may guess from the
name it is responsible for dynamically loading any modules, components. or
services on an as needed basis. This has the advantage of providing simple
access to powerful libraries but only having their code loaded when the code
is actually needed. Thus if a certain conditional is never met, the overhead
of loading the library will never be encountered.
If you have not yet read about it, then the use of ->do may have you wondering about its purpose. The ->do resolution is necessary because by default all objects
and services when retrieved are returned with a wrapper layer. This is to
bypass an issue in PHP where functions and methods return copies of objects
instead of a reference. This is dangerous when singleton objects are being
manipulated that internally keep track of such things as session data or other
such single instance data. At any rate, this technique may very well be
deprecated (although compatability will be retained) with the release of PHP 5
which according to documentation will always return object references.
Finally we can get to describing the actual structure of the declaration. The
declaration is fulfilled by a single parameter to the load method of the dynamic loader. This parameter is an array of
component type declarations where the key of the component type declaration is
one of data, logic, or render. Within each type
declaration is an array declaring all of the constituent components of that
type (it is not unreasonable to have multiple data retrieval or processor
components). The following illustrates the generic structure:
Load:
Data: Data Component 1 Data Component 2 ...
Logic: Logic Component 1 Logic Component 2 ...
Render: Render Component 1 Render Component 2 ...
There are several fields available for describing a
component. Only the source field is mandatory
and the rest are optional (though some restrictions may apply). A description
of each field follows.
Set this to the identifier that you would like to use in
components to retrieve other components that have been bound to the module.
For instance in a render component it is common
to retrieve the data or logic component for retrieving and processing any data before it is
output.
// // Obtain the newsFeed data object. // $loader = $this->getObject( 'newsFeed' );
// // Request the news entries from the data loader component. // $newsEntries = $loader->do->retrieveNews();
The above example also illustrates the fact that if you are going to create multiple interchangeable components then they must conform to some kind of interface. This is not unlike what is done for the Java programming language with one major difference, the interface is implicit and not defined explicitly except (hopefully) by documentation.
Set the value of this attribute to the location of the
source file containing the component class definition. If the fileName path begins with ///
then the path will be considered relative to the coreRoot path that was set in the project configuration. If instead it
begins with // then the path will be considered
relative to the codeRoot path that was set in
the project configuration. If the path is preceded by / then the component file will be retrieved relative to the root
of the filesystem. Last, if none of the former cases are true, then the path
will be treated as though it had been prefixed with //. You should make sure that read permission is set for the
appropriate owners at this location.
Set this to the name of the class for which a component
object should be instantiated. For instance in our example we would use
ProjectNewsFeedDisplay for the render
component. This parameter is optional IF the source path follows a specific convention. The convention requires
that the name of the class be the camel notation markup of the concatenation
of the path parts. For instance the path //project/newsFeed/display.inc would result in the class name
of ProjectNewsFeedDisplay which is exactly what
we used for the example and subsequently why we can omit the class attribute. Note that if you use an absolute path then
you lose out on a lot of flexibility and portability of your code since the
class name would need to be the same as the ALL the parts of the absolute
path, regardless of whether it resolves to a sub-path of the codeRoot.
Whether you are using the file-based or database property
system, the value of this attribute overrides the default propertyPath that may have been defined for the component. The
advantage of doing this is to create exceptional instances of a component that
inherits all of the normal property values and for which some have been set
differently. For an in depth discussion of the property system please view the
Property System Documentation.
As well as the ability to assign a property set to a
component via the propertyPath attribute, you
may also declare individual properties and their values using the
properties attribute. Individually defined
properties are added to the component after the propertyPath is evaluated, and so this also serves as a mechanism for
overriding properties values in the component's declaration.
'properties' => array
(
array
(
'group' => 'urls',
'name' => 'sourceUrl',
'value' => 'http://www.somegreatnews.cmx/feed.xml',
),
),
urls
and name sourceUrl has been assigned the given
value. This property might be accessed in the component as follows:
$xmlFeed = file( $this->urls['sourceUrl'] );
group entry is optional and if it
had been omitted then we would have the following to retrieve the XML feed
instead:
$xmlFeed = file( $this->sourceUrl );
We have only clipped the tip of the iceberg so far. As powerful as modules are, they represent only a small part of the InterJinn framework. Soon you will learn how to create services, transparently connect to user profile information, session information, forms, and how to work with multiple databases in a clear and concise fashion.