wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
howto.doxy
Go to the documentation of this file.
1 /** @page howto
2  *
3  * @section howto HowTos
4  *
5  * - @ref howtotype
6  * - @ref howtonode
7  * - @ref howtocontroller
8  * - @ref howtoviews
9  * - @ref howtoapplication
10  * - @ref howtorights
11  * - @ref howtoconcurrency
12  * - @ref howtoi18n
13  * - @ref howtologdebug
14  * - @ref howtoerror
15  *
16  * @subsection howtotype Definition of the data model
17  *
18  * The data model defines the domain classes. In principle the following steps are necessary
19  * to to make the domain classes available in the application:
20  *
21  * -# Definition and creation of the database tables.@n
22  * -# Implementation of the PersistenceMapper classes, which map the domain classes to the tables.@n
23  * -# Creation of special domain classes, if the Node class is not sufficient.@n
24  *
25  * Basically it doesn't matter, in which format the application data is stored. To load and store
26  * data application developers solely have to communicate with the PersistenceFacade.
27  * The PersistenceMapper classes have the actual knowledge of how to access the data.
28  * This abstraction makes it for instance possible to easily migrate the data storage from file based
29  * to databased. Only the mappers have to be exchanged. On the other hand it's also possible - an
30  * adequate implementation of the mappers assumed - to access arbitrary database schemes, which
31  * eases the connection of the wCMF to existing data sources.
32  *
33  * In practice it has been proved as advantageous to use a database as storage medium, because it has
34  * a better performance and can handle bigger amounts of data much better than XML files can.@n
35  * It is recommended to define one table for each domain class in the database scheme (e.g. @em author, @em article).
36  * On one hand it makes the import and export from and to other applications easier and on the other
37  * hand the access to the data is optimized compared to storing all data in one table, because then
38  * additional joins would be required to determine data types and other meta information. In addition the
39  * table columns can be more easily adjusted to the needs of the domain classes (in an extreme case all data
40  * must be stored as BLOB in an universal data table). NodeToSingleTableMapper implements the storage of all
41  * data in one table, which - for the said reasons - isn't recommended.
42  *
43  * Proceeding on these assumptions the creation of the data model becomes a lot simpler.
44  *
45  * @subsubsection howtotables Definition of the database tables
46  *
47  * We start with step 1. Assuming we're in the fortunate situation of defining the database tables
48  * ourselves. Then we proceed as follows.
49  *
50  * - For each domain class we create one table.
51  * - For each attribute of a domain class we define a row with the appropriate datatype.
52  *
53  * Further attributes follow a scheme, which is predefined by the NodeUnifiedRDBMapper. This
54  * makes the creation of the mappers easier:
55  *
56  * - Each table gets a primary key named @em id (int(11))
57  * - If one table depends on another, it gets a foreign key @em fk_table_id (int(11)), where
58  * @em table stands for the name of the parent table.
59  *
60  * If an N to M relation should be established between two domain classes (tables), a connection
61  * table must be defined. This table contains two primary keys, which point to the two connected
62  * tables.
63  *
64  * As an example serves a domain class @em Article, which is related to an @em Author:
65  * @verbatim
66  CREATE TABLE `Article ` (
67  `id` int(11) NOT NULL default '0',
68  `fk_author_id` int(11) default NULL,
69  `headline` VARCHAR(255),
70  `text` TEXT,
71  `sortkey` INT(3),
72  PRIMARY KEY (`id`)
73  ) TYPE=MyISAM; @endverbatim
74  *
75  * If the database tables already exist, this step is skipped. This however means, that more work
76  * must be put into the implementation of the PersistenceMapper.
77  *
78  * @subsubsection howtomapper Implementation of the PersistenceMapper
79  *
80  * Of course it's always possible to write own mappers. These must inherit from PersistenceMapper
81  * and implement the abstract methods given there.
82  * Since the framework already provides mappers, it's easier to use these as base classes. In case of a relational
83  * database it's possible to derive custom mappers from NodeRDBMapper. Then the methods for defining
84  * the SQL statements must be implemented.
85  *
86  * Assuming the database tables are created by the scheme above, we can proceed to the next step
87  * and use NodeUnifiedRDBMapper as baseclass for our mappers. After that we have to implement the
88  * methods RDBMapper::getType, NodeRDBMapper::createObject, NodeUnifiedRDBMapper::getTableName,
89  * PersistenceMapper::getPkNames, NodeUnifiedRDBMapper::getMyFKColumnNameImpl and
90  * NodeUnifiedRDBMapper::getObjectDefinitionImpl. These methods merely ask for properties of
91  * the domain class. The example shows the mapper for the table @em Article mentioned above:
92  * @verbatim
93  class ArticleRDBMapper extends NodeUnifiedRDBMapper
94  {
95  /**
96  * @see RDBMapper::getType()
97  */
98  function getType()
99  {
100  return 'Article';
101  }
102  /**
103  * @see NodeRDBMapper::createObject()
104  */
105  function &createObject($oid=null)
106  {
107  return new Article($oid);
108  }
109  /**
110  * @see NodeUnifiedRDBMapper::getTableName()
111  */
112  function getTableName()
113  {
114  return 'Article';
115  }
116  /**
117  * @see PersistenceMapper::getPkNames()
118  */
119  function getPkNames()
120  {
121  return array('id' => DATATYPE_IGNORE);
122  }
123  /**
124  * @see NodeUnifiedRDBMapper::getMyFKColumnNameImpl()
125  */
126  function getMyFKColumnNameImpl($parentType)
127  {
128  // start from the most specific
129  if ($this->getType() == 'Article' && $parentType == 'Author') return 'fk_author_id';
130  if ($parentType == 'Author') return 'fk_author_id';
131  return '';
132  }
133  /**
134  * @see NodeUnifiedRDBMapper::getOrderBy()
135  */
136  function getOrderBy()
137  {
138  return array();
139  }
140  /**
141  * @see NodeUnifiedRDBMapper::getObjectDefinitionImpl()
142  */
143  function getObjectDefinitionImpl()
144  {
145  $nodeDef = array();
146  $nodeDef['_properties'] = array
147  (
148  array('name' => 'is_searchable', 'value' => false),
149  );
150  $nodeDef['_datadef'] = array
151  (
152  /*
153  * Value description:
154  */
155  array('name' => 'id', 'app_data_type' => DATATYPE_IGNORE, 'column_name' => 'id',
156  'db_data_type' => 'INT(11) NOT NULL', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
157  'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
158  /*
159  * Value description:
160  */
161  array('name' => 'fk_author_id', 'app_data_type' => DATATYPE_IGNORE, 'column_name' => 'fk_author_id',
162  'db_data_type' => 'INT(11)', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
163  'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
164  /*
165  * Value description:
166  */
167  array('name' => 'headline', 'app_data_type' => DATATYPE_ATTRIBUTE, 'column_name' => 'headline',
168  'db_data_type' => 'VARCHAR(255)', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
169  'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
170 
171  ...
172  );
173  $nodeDef['_ref'] = array
174  (
175  /*
176  * Value description:
177  */
178  array('name' => 'author_name', 'ref_type' => 'Author', 'ref_value' => 'name', 'ref_table' => 'Author',
179  'id_column' => 'id', 'fk_columns' => 'fk_article_id', 'ref_column' => 'name')
180  );
181  $nodeDef['_parents'] = array
182  (
183  array('type' => 'Author', 'is_navigable' => false, 'table_name' => 'Author', 'pk_columns' => array('id'),
184  'fk_columns' => 'fk_author_id')
185  );
186  $nodeDef['_children'] = array
187  (
188  array('type' => 'Image', 'minOccurs' => 0, 'maxOccurs' => 'unbounded', 'aggregation' => false, 'composition' => true,
189  'is_navigable' => false, 'table_name' => 'Image', 'pk_columns' => array('id'), 'fk_columns' => 'fk_article_id',
190  'order_by' => array())
191  );
192  return $nodeDef;
193  }
194  } @endverbatim
195  *
196  * The method NodeUnifiedRDBMapper::getObjectDefinitionImpl is the most important method. It supplies an
197  * associative array, in which the attributes of the domain class (@em _datadef) the parent and the child
198  * domain classes (@em _children) are defined. In addition the Article contains a reference to
199  * the name of the Author (@em _ref), which allows to access the Author without loading it.@n
200  * Detailed information on the implemetation can be found under NodeUnifiedRDBMapper::getObjectDefinitionImpl.
201  *
202  * For configuring the domain classes in the configuration file see @ref sectypemapping.
203  *
204  * @subsubsection howtodomainclass Creating the domain classes
205  *
206  * The framework's PersistenceMapper work with the class Node, a subclass of PersistentObject.
207  * Different domain classes are created by specifying the type attribute (Node::getType) and
208  * a set of attributes (Node::getValueNames).
209  * So the class Node is a generic data container, into which data can be put by the method
210  * Node::setValue and retrieved by the method Node::getValue. For most applications this will
211  * do.
212  *
213  * If however more specialized domain classes are required for the application, they must
214  * be created by the PersistenceMapper. In this case subclasses of NodeRDBMapper must
215  * override the method NodeRDBMapper::createObject, which in turn creates instances of the
216  * special domain class.
217  *
218  * @subsection howtonode Working with the class Node
219  *
220  * @subsubsection howtoload Loading a data structure
221  *
222  * @code
223  $persistenceFacade = &PersistenceFacade::getInstance();
224 
225  // load model
226  $oid = PersistenceFacade::composeOID(array('type' => 'section', 'id' => array('1')));
227  $node = &$persistenceFacade->load($oid, BUILDDEPTH_INFINITE);
228  if ($node == null)
229  Message::error("A Node with object id ".$oid." does not exist.";
230  @endcode
231  *
232  * The example loads the section node with id 1 and all children nodes. If the oid of the
233  * requested node is not known either use the appropriate methods of PersistenceFacade
234  * (e.g. PersistenceFacade::getOID) or in more complex cases do an ObjectQuery.
235  *
236  * @subsubsection howtoiter Traversing a data structure
237  *
238  * @code
239  $iterator = new NodeIterator($node);
240  while(!($iterator->isEnd()))
241  {
242  $currentObject = &$iterator->getCurrentObject();
243  Message::trace($currentObject->getOID());
244  $iterator->proceed();
245  }
246  @endcode
247  *
248  * The example outputs the object ids of all nodes that are descendents of $node.
249  *
250  * @subsubsection howtooutput Output a data structure into different formats
251  *
252  * @code
253  $iterator = new NodeIterator($node);
254 
255  // Output into dot format
256  $filename = "graph.dot";
257  $dotOS = new DotOutputStrategy($filename);
258  $ov = new OutputVisitor($dotOS);
259  $ov->startIterator($iterator);
260 
261  iterator->reset($node);
262 
263  // Output into XML format
264  $filename = "graph.xml";
265  $xmlOS = new XMLOutputStrategy($filename);
266  $ov->setOutputStrategy($xmlOS);
267  $ov->startIterator($iterator);
268  @endcode
269  *
270  * @subsubsection howtomodify Modifying a data structure
271  *
272  * @code
273  $node->setValue('text', 'hello world!', DATATYPE_ATTRIBUTE);
274 
275  // Saving changes
276  // If one node has been modified
277  $node->save();
278 
279  // If several connected nodes have been modified
280  $iterator = new NodeIterator($node);
281  $cv = new CommitVisitor();
282  $cv->startIterator($iterator);
283  @endcode
284  *
285  * @subsection howtoviews Programming the views
286  *
287  * Views are implemented as HTML pages (defined in the view templates), which typically contain a form,
288  * which displays the data to be modified. For programming dynamic parts and to access
289  * application data the <a href="http://smarty.php.net/" target="_blank">Smarty</a> template
290  * language is used.@n
291  * By default the views are stored as .tpl files in the directory /application/inlcude/views
292  * (see @ref secsmarty). In the directory /wcmf/application/views those views are stored, which
293  * the framework uses for its standard application. These are the basis for the programming of
294  * custom views.@n
295  *
296  * In the view templates all data, which was passed to the view instance is accessible (see
297  * @ref howtocontroller). In the simplest case these can be displayed via @em {$variable}. In
298  * addition object data can be accessed by using @em {$object->getValue(...)}. By setting
299  * @em debugView = 1 (see @ref seccms) in the configuration file Smarty will display the data,
300  * which is available in the template, in an external window.@n
301  *
302  * The data displayed in the view's form is available to the following controller. Some
303  * (hidden) input fields should always exist. They are defined in the file
304  * /wcmf/application/views/formheader.tpl, which - to simplify matters - should be reused.@n
305  * For handling the form data some JavaScript functions are provided (and documented) in
306  * the file /wcmf/blank/script/common.js.
307  *
308  * In the directory /wcmf/lib/presentation/smarty_plugins the framework defines extensions
309  * of the Smarty template language:
310  *
311  * - Function @em translate for localization of strings@n
312  * e.g.: {translate text="Logged in as %1% since %2%" r0="$login" r1="$logindate"}
313  * - Function @em sessionvalue to get a session variable@n
314  * e.g.: {sessionvalue name="platform"}
315  * - Resource @em lib to integrate templates with a relative path to the framework's
316  * root directory (/wcmf)@n
317  * e.g.: {include file="lib:application/views/formheader.tpl"}@n
318  * ...
319  *
320  * @subsection howtocontroller Programming the controllers
321  *
322  * Controllers execute the user-defined actions. In order to implement custom controllers
323  * a class must be derived from the baseclass Controller, which implements the methods
324  * Controller::hasView and Controller::executeKernel.
325  *
326  * The Request instance passed to the Controller::initialize method provides all data of the
327  * preceeding view's input fields to the controller. The names of the input fields are the names
328  * of the request values. The controller in turn can pass data to the view by setting them on
329  * the Response instance.
330  *
331  * The method Controller::hasView returns @em true or @em false, whether a view is displayed
332  * or not (the return value can differ depending on the context or action, for an example see LoginController).@n
333  * The method Controller::executeKernel executes the actual action. In this method application
334  * data is loaded, modified, created, deleted and where required passed to the view for display
335  * or to the next controller to proceed.
336  * The method either returns @em false, which means, that the ActionMapper should call no further
337  * controller or true. In the latter case the ActionMapper determines the next controller
338  * from the context and action values of the response (see @ref actionkey).
339  * This means if a view should be displayed, the method must return @em false.
340  *
341  * While programming custom controllers often the methods Controller::initialize and
342  * Controller::validate are overridden in order to carry out initializations or to
343  * validate provided data.
344  *
345  * The framework's controllers are located in the directory /wcmf/application/controller.
346  *
347  * @subsection howtoapplication Programming the application
348  *
349  * A web application typically consists of several input masks (views), which
350  * are used to create, modify and delete data. The application is defined by the actions
351  * executable in the individual input masks. Thereby the framework makes no difference
352  * between actions used for data handling and those used to navigate or e.g. initiate the
353  * export of data.
354  *
355  * The definition of an action requires the following steps:
356  *
357  * -# @ref howtoapplication1
358  * -# @ref howtoapplication2
359  * -# @ref howtoapplication3
360  * -# @ref howtoapplication4
361  * -# @ref howtoapplication5
362  *
363  * As an example we use the action for displaying an @em article node in order to
364  * edit it. Let's look at the individual steps:
365  *
366  * @subsubsection howtoapplication1 Definition of the action name
367  * We name the action @em editArticle. This name need not to be unique in the whole
368  * application. The ActionMapper only requires the name (and the @ref actionkey defined
369  * by the action) to find the next appropriate controller.
370  *
371  * @subsubsection howtoapplication2 Creating the button to trigger the action
372  * In order to display the data the application must know which article is selected.
373  * This is exactly defined by it's @ref oid. The data transfer between the input
374  * masks is achieved by the HTTP POST mechanism, i.e. a (hidden) input field must exist,
375  * which contains the oid of the article to be displayed. Since for most applications it's often
376  * necessary to transfer an oid, the framework defines a standard field @em oid in each
377  * view (see file /wcmf/application/views/formheader.tpl), which can easily be set
378  * by the JavaScript function @em doDisplay (/wcmf/blank/script/common.js).
379  *
380  * The action is triggered upon submission of the input form. Another JavaScript function
381  * (@em submitAction) simplifies the execution. The form data is passed to the main.php
382  * script, which delegates the further execution to the ActionMapper. The link to execute
383  * the action could look like this:
384  * @verbatim <a href="javascript:setContext('article'); doDisplay('{$article->getOID()}');
385  submitAction('editArticle');">{translate text="edit"}</a> @endverbatim
386  * For details on programming the views see @ref howtoviews.
387  *
388  * @subsubsection howtoapplication3 Customizing the configuration file
389  * To determine the controller, which carries out the action, the ActionMapper requires
390  * an appropriate entry in the configuration file (see @ref secactionmapping). If
391  * the controllers name is @em ArticleController, the entry could look like this:
392  * @verbatim
393  [actionmapping]
394  ??editArticle = ArticleController @endverbatim
395  * Don't forget to introduce the ArticleController in the configuration section @ref secclassmapping.
396  *
397  * Additionally the ArticleController should display a view for editing the article. If we
398  * name this view @em article.tpl, the configuration entry would look like the following (see
399  * @ref secviews):
400  * @verbatim
401  [views]
402  ArticleController?? = article.tpl @endverbatim
403 
404  * @subsubsection howtoapplication4 Implementing the action as a controller
405  * The action is executed in the controller - in this example in the @em ArticleController class.
406  * Since the controller should display a view with the article's data, we first must specify that
407  * the controller has a view and second the data of the article must be passed to the view.@n
408  * At first however it must be assured, that the controller receives an oid. This happens in
409  * the method Controller::validate, which searches for the entry in the passed data:
410  * @verbatim
411  function validate()
412  {
413  if ($this->_request->getValue('oid') == '')
414  {
415  $this->setErrorMsg("No 'oid' given in data.");
416  return false;
417  }
418  return true;
419  } @endverbatim
420  *
421  * We declare the existence of a view in the method Controller::hasView:
422  * @verbatim
423  function hasView()
424  {
425  return true;
426  } @endverbatim
427  *
428  * Finally the action is executed in the method @em Controller::executeKernel. Here the
429  * controller loads the data and provides it to the view for display by setting it on the
430  * response instance:
431  * @verbatim
432  function executeKernel()
433  {
434  $persistenceFacade = &PersistenceFacade::getInstance();
435 
436  // load model
437  $article = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
438 
439  // assign model to view
440  $this->_response->setValue('article', $article);
441 
442  // stop processing chain
443  return false;
444  } @endverbatim
445  *
446  * It's important that the method returns false, since this causes the ActionMapper
447  * to end the execution and wait for user input. The display of the view is done by
448  * the framework.
449  *
450  * @subsubsection howtoapplication5 Displaying data in the view
451  * After the controller has provided the view with the data, the view can display the data.
452  * In our case after the ArticleController has been executed a variable @em article is
453  * known to the view, which matches the article node.@n
454  * The programming of the views is done in HTML together with the Smarty template language.
455  * The file @em article.tpl could contain the following line:
456  * @verbatim article name: {$nodeUtil->getInputControl($article, "name")} @endverbatim
457  *
458  * In the curly brackets you can find Smarty code, which calls the method NodeUtil::getInputControl.
459  * This method displays the input control (in our case a textfield), which corresponds to the
460  * article's @em name attribute, in the HTML page. In the same manner the other attributes can be handled.
461  *
462  * @subsection howtorights Rights management
463  *
464  * Rights can be assigned to the execution of actions in the user interface or to the editing
465  * of domain classes and individual objects (instances of domain classes). For editing content the framework
466  * defines the rights @em read, @em modify, @em delete and @em create.@n
467  * The authorization for actions in the user interface is handled by the ActionMapper, for actions
468  * concerning the data the PersistenceMapper checks the permissions (and sets objects, for which
469  * the right @em modify is not set, to non-editable (@em is_editable = false) ).
470  * Both classes use the method RightsManager::authorize and generate a fatal error message,
471  * if authorization fails (see @ref howtoerror).
472  * To prevent this the rights can be retrieved directly in order to take appropriate messures:
473  * @verbatim
474  $rightsManager = &RightsManager::getInstance();
475  if ($rightsManager->authorize($this->_request->getValue('oid'), '', ACTION_READ))
476  {
477  $object = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
478  }
479  else
480  {
481  // do something else if the user cannot read the object
482  } @endverbatim
483  *
484  * In the example the object is only loaded, if it's permitted. For the definition of rights
485  * see @ref secauthorization.
486  * @note If @em anonymous is set to one in the configuration file, the rights management
487  * is disabled (see @ref seccms).
488  *
489  * @subsection howtoconcurrency Concurrency
490  *
491  * If more than one user works with the application at the same time, conflicts can occur, if two
492  * users want to edit the same object concurrently. In this case the first user can request
493  * a lock on that object, which only allows reading access to succeeding users. This lock will
494  * be transfered to all instances loaded in the future as long until the user unlocks the object,
495  * which happens automatically upon execution of an action (call to main.php).
496  * In the sourcecode this looks as follows:
497  * @verbatim
498  $lockManager = &LockManager::getInstance();
499  $object = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
500 
501  // if the object is locked by another user we retrieve the lock to show a message
502  $lock = $object->getLock();
503  if ($lock != null)
504  {
505  $lockMsg .= $lockManager->getLockMessage($lock, $recipe->getName());
506  }
507  else
508  {
509  // try to lock object
510  $lockManager->aquireLock($this->_request->getValue('oid'));
511  } @endverbatim
512  *
513  * The functionality described above is imlemented in the method LockManager::handleLocking,
514  * with the only difference that this method only acquires a lock in cases in which the
515  * user is allowed to modify the object.@n
516  * An object, which is loaded by another user is set to non-editable (@em is_editable = false)
517  * automatically upon loading.
518  *
519  * @subsection howtoi18n Multilanguage Support
520  *
521  * For localization of the application the method Message::get is provided. For a given (english)
522  * string this method searches for the required language's version. The language version can
523  * either be passed directly with the method call or can be set application wide (see documentation
524  * of Message::get).@n
525  * How the translation is retrieved depends on the parameter @em usegettext (see @ref seccms).
526  * If it is set to 1, the method uses the PHP function @em gettext. This in turn makes use of
527  * *.mo files in the directory /localeDir/language/LC_MESSAGES - e.g. /locale/de_DE/LC_MESSAGES/main.mo
528  * (see documentation of gettext).@n
529  * If gettext doesn't exist, the language version can also be taken from an associative array named
530  * messages_language (e.g. messages_de_DE).
531  * This must be defined in a file /localeDir/language/LC_MESSAGES/messages_language.php (e.g.
532  * /locale/de_DE/LC_MESSAGES/messages_de_DE.php). The keys of the array are the strings, which are
533  * passed to the method Message::get, the values are the corresponding translations.@n
534  * @note If a translation for a text doesn't exist, the string, which was passed to the method
535  * Message::get, will be used.
536  *
537  * For making the localization more comfortable some tools are provided in the directory /wcmf/tools/i18n:
538  * - @em locale.php extracts all strings to be localized (where the method Message::get is used) from the
539  * application (views, controllers) and creates a *.po file for use with gettext.
540  * - @em po2array.php generates the array definition described above from a *.po file.
541  *
542  * For localizing the view templates a Smarty plugin is provided, which is to be used as follows:
543  * @verbatim {translate text="Logged in as %1%" r0=$authUser->getLogin()} @endverbatim
544  *
545  * The @em parameters of the method Message::get are defined by the values of @em r0, @em r1, ...
546  *
547  * @subsection howtologdebug Debugging/Logging
548  *
549  * For debugging und logging output the <a href="http://logging.apache.org/log4php/" target="_blank">log4php</a>
550  * framework is used. For convenient usage wCMF defines a thin wrapper class called Log. To log a debug message
551  * in the category of the current class, just call:
552  *
553  * @verbatim Log::debug($message, __CLASS__); @endverbatim
554  *
555  * @subsection howtoerror Error handling
556  *
557  * In the application two types of errors are distinguished:
558  * - @em Fatal: Errors, which are so critical, that it's not possible to proceed with the current action
559  * (e.g. the missing of a controller).
560  * - @em Non-fatal: Errors, which merely demand a notification to the user (e.g. invalid input)
561  *
562  * These errors can be produced in the following ways:
563  *
564  * - @em Fatal: The use of WCMFException::throwEx calls a global error handling routine:
565  * @code onError($message, $file='', $line='') @endcode
566  * - @em Non-fatal: adding a message by calling Controller::appendErrorMsg makes this message - together with
567  * all accumulated messages before and those to follow - available in the next view's @em $errorMsg variable.@n
568  * In order to delete old messages the method Controller::setErrorMsg class must be called with an empty string
569  * parameter.
570  *
571  * Many classes define a method @em getErrorMsg, which provides more detailed information on the cause of an error.
572  *
573  * Back to the @ref intro | Previous section @ref dbschema | Next section @ref howtostart
574  *
575  */