MetaL logo

Metastorage tutorial


Page version


Metastorage tutorial

    This tutorial is meant to help developers getting started quickly with Metastorage and take advantage of its potential to develop their software projects faster, with great reliability and with much less effort than using traditional development methods, on which all code is written manually.

    Although this tutorial will guide you through the basic steps to start developing applications with Metastorage immediately, you are also recommended to read Metastorage reference manual to take full advantage of the capabilities of this tool.

  • Introduction
  • Metastorage is a tool that is meant to simplify the development of applications that store and retrieve persistent data. The data becomes persistent when it is stored by one application in some container. Then it may be retrieved later by the same or other applications, even after the application that originally stored the data was terminated.

    Currently, Metastorage only supports the storage of data in SQL based relational databases. However, in the future it will also support storing and retrieving data in flat file databases, XML files or data accessible via LDAP servers.

    Metastorage generates code in the form of a package made of object oriented programming (OOP) classes. This package of classes is also referred in Metastorage terminology as a component.

    The component classes constitute an application programming interface (API) that is ready to be used in applications that need to create, retrieve, update and delete (CRUD) persistent data.

    The code generated by Metastorage is based on a description of a component data model. The component description is defined in files with a XML based format named Component Persistence Markup Language (CPML).

    The CPML format allows the definition of multiple classes with data variables, relationships between the classes, validation rules and the functions that provide access to the objects of the generated classes.

    Additionally, Metastorage may also generate classes that handle forms to provide a Web interface to access the persistent objects.

  • Requirements
    • MetaL engine
    • Metastorage is an application of the MetaL meta-programming language. To use Metastorage, you need to obtain and install the MetaL compiler engine. See ahead to know how to obtain and install the MetaL compiler engine.

    • PHP
    • Actually, Metastorage is made of PHP language scripts that call the MetaL compiler engine, which is also a PHP package. If you do not have PHP available in your development environment, you may obtain it from the download page of the PHP site.

      PHP is mostly known by its applications that serve dynamically generated pages using a Web server. However, you do not need a Web server to run Metastorage. You just need to execute the Metastorage main script, using either the command line (CLI) or the CGI executable versions of PHP.

      Look in your PHP installation for an executable program named php. If you are using a Unix/Linux system, the php executable program is possibly located in the default installation directory: /usr/local/bin/php. If you are using Windows, php.exe executable program is located in the PHP installation directory.

    • Memory limit
    • The MetaL compiler engine works by processing pipelines of generated data. Such data is stored in arrays. These arrays of data may take a substantial amount of computer memory during the data processing. Depending on the complexity of your project and the PHP version that you use, you may need at least 16MB of memory.

      The amount of memory that is needed to run the MetaL compiler may exceed the limit set in your PHP configuration file. When that happens, PHP aborts the execution of the script that called the MetaL compiler classes.

      To avoid the problem, you need to increase the memory_limit option in your php.ini configuration file. Usually this file is located in /usr/local/lib/ directory in Unix/Linux systems or C:\WINDOWS\ in Windows systems.

      If you do not find the configuration file in these directories, execute the command php -i >phpinfo.html. The CGI version of this command outputs a HTML page to the file phpinfo.html listing your current configuration options. The location of the php.ini file is mentioned right in the beginning of this page.

  • Troubleshooting
    • Since Metastorage is a tool that depends on other applications, like for instance PHP, there may be problems using Metastorage due configuration details, misunderstanding of Metastorage features, or bugs that were not yet discovered and fixed.

      In case of difficulty, there is a Metastorage troubleshooting guide document that provides solutions for these and other types of problems that you may encounter while trying to use Metastorage. It also contains information on how to obtain personal support to help you to solve problems that are not documented.

      Please always check this document first every time you find problem that you are not able to solve by yourself.

  • Installation
    • Downloading Metastorage and MetaL
    • Since Metastorage is based on MetaL, you need to download the archive that contains both MetaL and Metastorage packages. This archive is available from the download page of the MetaL site, either in .tar.gz or .ZIP archive formats.

      Metastorage expects to find the standard MetaL distribution in the parent directory of metastorage sub-directory. It includes the MetaL engine in metal, a XML parser component in xmlparser, a forms generation component in forms, Metabase in metabase, a Web based directory browser component in directorybrowser and a command line arguments processor in readarguments.

      To install Metastorage, you just need to extract the bundle archive with MetaL and Metastorage packages in a directory of your choice.

      Enter in the metal directory and then you can see the Metastorage application located inside a sub-directory named metastorage.

    • Obtaining Metastorage and MetaL via CVS
    • You may obtain the most recent versions of MetaL and Metastorage via CVS. The CVS repository contains development versions. This means that you may find there versions that have features that were not yet announced. Usually such versions are not fully documented and may not be working properly. Therefore, use the latest CVS versions with caution.

      The CVS server is located at: cvs.meta-language.net. The connection method is pserver. The complete connection string is:

      :pserver:cvsread@cvs.meta-language.net:/opt2/ena/metal

      You may use a CVS GUI program like TkCVS or WinCVS or the command line version of CVS.

      First you login entering an empty password:

      cvs -d :pserver:cvsread@cvs.meta-language.net:/opt2/ena/metal login

      Then you checkout the necessary modules in a directory of your choice:

      cvs -z3 -d :pserver:cvsread@cvs.meta-language.net:/opt2/ena/metal checkout metal xmlparser forms metabase readarguments directorybrowser metastorage

      Alternatively, the download page also has contains snapshot archives of the latest version in the CVS repository. These archives are named metastorage-cvs.tar.gz and metastorage-cvs.zip.

    • Testing Metastorage installation
    • Once you have obtained and installed Metastorage, whether it was via normal download or checking out from the CVS repository, you should test your installation to be sure that you can proceed and start using Metastorage.

      Metastorage comes with an example project that consists mainly of a file named cms.component. To test Metastorage installation, just try to build the project defined by this file following the steps described in the section about generating component code.


  • Designing a persistent component
    • Creating a component
      • Once you have successfully installed Metastorage, you are ready to start using it by designing the model of your application components.

        It is never too much to stress the importance of planning before moving to the implementation of your applications, especially of medium or large size projects. When you do proper planning, you can minimize the need to redesign your application at a later stage when you realize that the current design does not satisfy the application requirements.

        It is outside of the scope of this tutorial to teach how to properly plan a software project. However, if you want to know more about good software project planning practices, you may want to study about: project requirements specification, risk analysis, use case analysis, system architecture and software component modeling and implementation.

        Once you have taken the necessary planning steps to determine which software components your application needs, you are ready to design such components. With a clear idea of what should be your application components, you can determine which will deal with persistent information, i.e. those that will be responsible for managing the information that will be stored in such way that it can be retrieved any time later, even after it has ended the session of your application that initially creates the information.

        The role of Metastorage is to generate the code that will manage components that handle the storage of persistent information. Metastorage generates code to implement your persistent components based on a specification of your data model defined in the CPML format. This specification defines describes several types of classes of objects that will constitute the API that your application will use to manage your persistent information.

        The CPML format is based on XML. The start tag is named component. Besides the component classes definition, it should include component meta-information fields that consist of a name and a description. These are required fields that are meant mostly for documentation purposes.

        A CPML component definition has this form:

        
         <?xml version="1.0"?>
         <component>
        
           <name>cms</name>
        
           <description>Content management system component</description>
        
           <!-- The rest of your component definition goes here -->
        
         </component>
        
        

        Component definitions may contain sections to define multiple entity data classes, an object factory class, a schema installation class and optional form handling classes. Each of these sections is described in the following items.

    • Entity data classes
      • The most important classes that are described in a component definition are the entity data classes. Data classes represent models of the entities that your application deals with.

        For instance, lets suppose that your application has content management system that deals with entities like content articles to be published, authors that write the articles, categories within which the articles are classified. Your data classes are responsible only for storing and retrieving the information that concerns these entities.

        Keep in mind that data classes are not responsible for determining how the information is manipulated by the application upon the interaction with the users or external systems. That is the role of software components that implement what is usually known as the business rules or application logic.

        For now, it is outside the scope of Metastorage to generate code to implement application logic from model definitions. However, the description of data classes may include definitions that are meant to support the implementation of the application logic, like the relationships between the objects of the data classes, or validation rules that impose constraints on the values of the data objects.

        The data classes are also not responsible for presenting the information of the objects to the application users or make it available to external systems, nor to provide means to let the users or external systems to edit the data objects. That is the role of components that generate reports or manage external interfaces.

        Currently, Metastorage provides means to generate code that handles specific user interface interactions via Web form handling classes. In future releases, there will also be support for generating code that will take care of the production of Web reports based on the information taken from the data objects.

        A component may have one or more data classes. The description of a data class may be done inside the component definition file using a section that starts with the tag class.

        Alternatively, a data class may be described in a separate XML file that should also start with the tag class. Components that use classes defined in separate files should use the tag includeclass to specify the name of the class definition file to include.

        Each class definition must include the tag name precisely to specify the class name. A class definition has the following form:

        
         <class>
        
           <name>article</name>
        
           <!-- The rest of your class definition goes here -->
        
         </class>
        
        

        Or this form, when using external class definition files:

        <includeclass>article.class</includeclass>

        Class definitions may contain sections to define multiple class variables, optional validation rules and class functions that may be needed to manipulate the class objects. Each of these sections is described in the following items.

      • Basic data type variables
      • The first thing that you need to define when you are designing entity data object classes, is what items of information you want store about your application entities. Such items of information are stored in variables of your data classes.

        A data class may have several variables. Each variable is defined in an individual class section that starts with the tag variable.

        A variable must have an unique name within its class. The name of a variable should be defined with the tag name.

        A variable may be of one of several supported basic data types: text, integer, boolean, float, decimal, date, time and timestamp. The tag type should be used to specify the type of a variable.

        By default, all text variables have an undefined length. However, when storing data objects in database tables, there may be an implicit size limit for text variables of undefined length. That limit is dependent of the type of database that is used.

        To specify the maximum length of a text variable explicitly, use length option. This option tells Metastorage to define a database schema that limits the number characters that are allocated to store the variable values.

        text variables that are meant to store values that span more than one line, need to be defined as multi-line. This is an optional definition parameter that is meant to hint Metastorage that this type of variable may occupy multiple lines, for instance in user interface forms. The value of the multiline option is a boolean. It tells whether or not it is a multi-line variable.

        The variables of the types float and decimal can be used to store values of values that may not be integer. The difference between these types is that the decimal is meant to be used with variables that need to store numbers that must be accurate, and so may not loose precision, like for instance values that represent money. Currently, there is no support to specify the precision limits of the variables of these types.

        The values of variables of the types date, time and timestamp should be strings that represent dates or times in the ISO format. This means that dates must have the format YYYY-MM-DD (Year, Month, and Day). The month is the number of the month from 01 to 12. Times must have the format hh:mm:ss (hour, minute, second).

        A timestamp variable can represent both the date and the time in a single value. It is represented by strings of the format YYYY-MM-DD hh:mm:ss.

        By default, all variable are required. This means that they have to be initialized to a value before an object can be stored. If a variable is not really required to store a concrete value, it may be defined as optional. Optional variables that are not initialized contain an undefined value usually known as null.

        Basic data type variables may also have an initial value. If an initial value is specified for a given class variable, every new object of the class will be created with that variable set to the specified initial value.

        Here is an example of a class variable definition:

        
         <variable>
        
           <name>body</name>
           <type>text</type>
           <optional>1</optional>
           <length>64</length>
           <initialvalue>some initial text value</initialvalue>
           <multiline>1</multiline>
        
         </variable>
        
        

        Some types of variables may be set automatically by the data object classes when the objects are created or updated. To make a variable be set with automatic values, just define them using the autocreate or autoupdate attributes.

        The use of the autocreate attribute determines that the variable will be set to an automatic value when an object is created and will never be changed. The autoupdate attribute determines that the variable will be set to an automatic value every time the object is updated, including when it is created.

        Here is an example of definition of class variables set to automatic values:

        
         <variable>
        
           <name>created</name>
           <type>timestamp</type>
           <autocreate>localtime</autocreate>
        
         </variable>
        
         <variable>
        
           <name>updated</name>
           <type>timestamp</type>
           <autoupdate>localtime</autoupdate>
        
         </variable>
        
        

        Currently, the applications can access directly to any variable of an entity data class generated by Metastorage, like with any public class variable. In the future, there will be an option to tell Metastorage to generate setter and getter functions that encapsulate the access to the variables and these are made private.

        Currently only variables of the types date, time and timestamp can be set to automatic values. These types of variables can be set to either localdate, utcdate, localtime and utctime.

      • Large data type variables (BLOBs)
      • Text variables may store information that in some cases is limited to no more than 255 characters. To store larger amounts of information it is necessary to use large data type variables.

        Large data type variables are like files. They cannot be stored or retrieved all at once like the other basic data types. It is necessary to use functions of type setlargedata and getlargedata.

        Large data type variables are declared specifying the type largedata. They can be of two variants: text (CLOB - Character Large OBjects) or binary (BLOB - Binary Large OBjects). By default, large data variables are binary.

        Here is an example of definition of a large data type class variable:

        
         <variable>
           <name>picture</name>
           <type>largedata</type>
           <binary>1</binary>
           <optional>1</optional>
         </variable>
        
        

      • Object IDentifiers (OID)
      • In typical database applications, tables that store information of application entities, usually have one integer field that is used to store an unique identifier number for each table row. In Metastorage, these unique identifiers are defined by variables that are known as Object IDentifiers (OID).

        Currently, you may not define explicitly OID class variables. Metastorage always assumes the definition of an OID variable of type integer and with the name id. In the future, it will be possible to define OID variables explicitly. Then, OID may also be defined by more than one class variable.

      • Reference variables (1-to-1 relationships)
      • When you design the different entities of your applications, often you realize they may be related with each other. For instance, in a content management system, each article is related to an author.

        The simplest type of relation between two entities is 1-to-1. This means that for instance, if you have two related entities A and B, for each object of the entity class A, there is at most one related object of entity class B, and vice-versa.

        For example, imagine that your application model has an entity called man and another called woman. If you assume a 1-to-1 husband-wife relationship, for each object of the entity class man there is at most one object of entity class woman that corresponds to the man's wife. Similarly, for each object of the entity class woman there is at most one object of entity class man that corresponds to the woman's husband.

        In Metastorage, 1-to-1 relationships are defined by reference variables that specify the related entity classes. The reference variables are like normal variables, except that instead of specifying their type, their definition specifies the name of the related class using the tag class.

        Here is an example of definition of two entity classes, man and woman, related by the reference variables wife and husband, respectively.

        
         <class>
        
           <name>man</name>
        
           <variable>
             <name>wife</name>
             <class>woman</class>
           </variable>
        
         </class>
        
         <class>
        
           <name>woman</name>
        
           <variable>
             <name>husband</name>
             <class>man</class>
           </variable>
        
         </class>
        
        

        A reference variable may also establish a relationship between two objects of the same entity class. For instance, if instead of having the entities man and woman we had a single entity class named person, we could have an entity variable named spouse that would reference to the wife in the case an object represented a man or the husband in the case an object represented a woman.

        Here is how that relationship between objects of the same entity class would be defined:

        
         <class>
        
           <name>person</name>
        
           <variable>
             <name>spouse</name>
             <class>person</class>
           </variable>
        
         </class>
        
        

        In practice, a reference variable of an object of an entity class contains the OID of the respective object of the related class.

        Like with basic data type variables, reference variables may also be defined as optional. An optional reference variable may either not be initialized or set to a null value.

        If a reference variable of an object of a given entity class is set to a null value, i.e. it is undefined, that indicates that the there is no related object of the other entity class that defines the relationship. For instance, if an object of class man has the reference variable wife set to null, that means that the man object identifies a man that is not married.

        Applications should not access reference variables directly to manipulate the respective objects. Instead you should declare and use functions of type setreference and getreference to assign or retrieve the related objects by storing or de-referencing the respective OID. See the section ahead about defining data objects functions to learn how to declare and use this type of functions.

      • External component class references
      • A reference variable may store an object of a class defined in another component. The component that defines external class must be indicated in the class variable section.

        
         <class>
        
           <name>article</name>
        
           <variable>
             <name>author</name>
             <class>user</class>
             <component>accounts</component>
           </variable>
        
         </class>
        
        

        Every external component that is referenced in a variable definition must be previously defined in special section of the current component, specifying the name of the component and its definition file name.

        
         <component>
           <name>accounts</name>
           <file>accounts.component</file>
         </component>
        
        

      • Object collections (1-to-many relationships)
      • An object of a given entity class may be related to many objects of another entity class.

        Consider for instance one writer that is the author of many articles published through a content management system. In this case we can say that between a writer and his articles there is a 1-to-many relationship. This type of relationship defines a collection of objects, that are the articles published by each writer.

        A collection may also be empty, meaning that it would not contain any objects. In this example it would be the case when a writer would not have published any articles yet.

        To define a 1-to-many relationship, you need to declare a collection section in the definition of the class that relates to many objects of the other class. You need also to specify in the class of those objects, the reference variable that points to the class that has the collection. The definition of the collection must specify the name of that reference variable name and its class name.

        To understand this better with a concrete example, consider the 1-to-many relationship between a writer that is the author of many articles. Here follows the definition of the writer and article entity classes. They establish a relationship defined by the collection named articles of the writer class that specifies the author reference variable to indicate the writer object to which each article object concerns.

        
         <class>
        
           <name>writer</name>
        
           <collection>
             <name>articles</name>
             <class>article</class>
             <reference>author</reference>
           </collection>
        
         </class>
        
         <class>
        
           <name>article</name>
        
           <variable>
             <name>author</name>
             <class>writer</class>
           </variable>
        
         </class>
        
        

        The definition of a collection is merely logic. This means that in practice the objects of the class that defines the collection, in this example the writer class, does not hold additional information about the relationship. Only the objects that make part of the collection, in this example of the article class, hold in the reference variable author the OID of the writer object that has the collection.

        When Metastorage needs to generate code that will traverse the list of all objects that belong to a collection, it generates the necessary statements that search for all objects that make part of the collection by looking at the respective reference variable checking if it is set to the OID of the object that has the collection.

      • Mutual collections (Many-to-many relationships)
      • Two entities may be related in such way that each of them may have collections of objects of each other classes. These mutual collections establish what is called a many-to-many relationship, i.e. one class is related to a collection of objects of the other and vice-versa.

        For instance, in content management system you may have published articles and categories that classify the articles. A category may have a collection of articles. An article may have a collection of categories within which it is classified.

        To declare a many-to-many relationship you just need to declare a collection in each of the entity classes referencing each other.

        Here follows the definition of the many-to-many relationship between the article and the category entities described above with the collections categories and articles set with reference pointing to the collection of the other class:

        
         <class>
        
           <name>category</name>
        
           <collection>
             <name>articles</name>
             <class>article</class>
             <reference>categories</reference>
           </collection>
        
         </class>
        
         <class>
        
           <name>article</name>
        
           <collection>
             <name>categories</name>
             <class>category</class>
             <reference>articles</reference>
           </collection>
        
         </class>
        
        

        The many-to-many relationships between two entities are implemented by Metastorage by the means of a virtual third entity. This virtual entity acts as if it had two 1-to-many relationships, one with each of the two real entities. Each of these two 1-to-many relationships is represented by two reference variables in virtual entity. These reference variables are used to point to the objects of the real entity classes.

        In practice this means that when Metastorage generates code to store the information of each entity class data objects in a database table, for each many-to-many relationship that may exist between two entities, there will be a third database table that will store the OID of the related objects of both real entity classes.

        Although in practice Metastorage generates code to access to the objects involved in the relationship transparently, in terms of usage of database tables, this would be equivalent to define three entities like this:

        
         <class>
        
           <name>category</name>
        
           <collection>
             <name>articles</name>
             <class>category_x_article</class>
             <reference>category</reference>
           </collection>
        
         </class>
        
         <class>
        
           <name>article</name>
        
           <collection>
             <name>categories</name>
             <class>category_x_article</class>
             <reference>article</reference>
           </collection>
        
         </class>
        
         <class>
        
           <name>category_x_article</name>
        
           <variable>
             <name>category</name>
             <class>category</class>
           </collection>
        
           <variable>
             <name>article</name>
             <class>article</class>
           </collection>
        
         </class>
        
        

      • Validation rules
      • Most applications need to enforce rules on the data that is manipulated, so they can work conveniently. Metastorage lets you define several types of validation rules that the objects of each class must obey.

        The validation rules are enforced by the means of a class function of type validate for which that Metastorage generates the necessary code from the respective class validation rules that you may define.

        For each class, you may specify multiple validation rules. Each validation rule is defined in an individual section within each class definition that must start with the tag validation.

        For each validation rule you need to specify the name of the validation type, an error code and eventual parameters that are specific to the type of validation that you are defining.

        Currently, the only supported types of validation are: notempty and unique. In the future, more types of validation will be supported.

        The error code is an integer number that must be unique among all the validation rules that you specify for each class. This error code is used to determine which rule failed when the class validate function determined that the variables of the specified object has invalid values.

        Here is an example of the definition of the class article with a validation rule that specifies that the variable title must not be empty.

        
         <class>
        
           <name>article</name>
        
           <variable>
             <name>title</name>
             <type>text</type>
           </variable>
        
           <validation>
             <type>notempty</type>
             <errorcode>1</errorcode>
             <parameters>
               <variable>title</variable>
             </parameters>
           </validation>
        
         </class>
        
        
        • notempty rule
        • The notempty rule is meant to verify that text variables are set to a non-empty string.

          This rule only requires one parameter, which is the name of the text variable that is meant to be checked. This parameter is defined with a tag named variable.

        • unique rule
        • The unique rule is meant to verify that there are no other objects of the same class with the same values in one or more given variables.

          This rule requires as parameters the name of variables that are meant to be checked. The name of each variable is specified with a tag named variable.

      • Data object functions
        • The specification of data object class functions is a very important topic. So, please pay attention to the following explanation, so you can make good use of Metastorage capabilities to generate adequate code for your entity classes.

          The purpose of the entity classes is to store data in its objects and manipulate the data according to the needs of the applications on which the objects are used. The manipulation of the data objects is meant to be done by the means of functions of the entity classes.

          Metastorage can generate code to implement many types of functions to manipulate data class objects for different purposes. However, generating the code for all types of functions that Metastorage supports would lead to very large classes. Such large classes would waste a lot of memory and time to load.

          On the other hand, not all entity classes need to have all the types of functions that Metastorage supports. It depends on the purpose of the applications that manipulate the objects of these entity classes.

          Therefore, it must be you, the designer of your application data model, that has to specify what kind of functionality your application needs. This way Metastorage can generate only the necessary code to implement your entity classes, making your application more compact and efficient.

          This is what is called the JE WIN code generation approach: generate Just Exactly What I Need.

          Metastorage can generate many types of functions, but you do not have to know or understand all of them in depth. For now, it is important that you just take an overview of the available types of functions. Later, you will start using them once you have a better idea of which you need to use in your applications.

          Initially, you only need the basic types of functions, like persist for storing an object, and eventually validate to verify that an object conforms to the class validation rules.

          To specify the functions that you need, you have to declare each function in a section within the class definition that must start with the tag function.

          Each function declaration must define the function name, the type and a function type specific parameters section that may be specified only when necessary. Here is an example of the definition of the class article with a function of type persist named save.

          
           <class>
          
             <name>article</name>
          
             <function>
               <name>save</name>
               <type>persist</type>
             </function>
          
           </class>
          
          

          The function name is the actual name that will be used to call the class function from within your application. The function type is any of the supported by Metastorage. There may be circumstances when your entity classes may have multiple functions of the same type. They only need to have distinct names.

          Here follows an overview of the available types of functions. For a more detailed explanation of the parameters of each type of function, please consult the class function description section of the Metastorage reference manual.

        • addtocollection function
        • A addtocollection type function adds to a collection defined in a class an object of the class specified in the collection definition.

        • cloneobject function
        • A cloneobject type function may be used to create copies of a given object.

        • custom function
        • A custom type function may be used to implement customizations of a class using arbitrary code written in the target language by the component designer.

        • delete function
        • A delete type function deletes the object from persistent storage if it was already persisted.

        • getcollection function
        • A getcollection type function retrieves all the objects that belong to a class collection of a given object.

        • getlargedata function
        • A getlargedata type function can retrieve the information stored in a large data type variable.

        • getreference function
        • A getreference type function retrieves an object referenced by a class variable of a given object.

        • persist function
        • A persist type function stores in the persistence container the changes made to a given object.

        • removefromcollection function
        • A removefromcollection type function removes from a collection defined in a class an object of the class specified in the collection definition.

        • setlargedata function
        • A setlargedata type function stores information in a large data type class variable.

        • setreference function
        • A setreference type function stores a reference to an object in the class variable of a given object.

        • validate function
        • A validate type function validates the consistency of the data of an object verifying all the validation rules defined for the class of the object.

    • Object factory class
      • The factory is a special class that is responsible for creating entity class data objects either from scratch or retrieving them from persistent storage.

        The data objects should never be created directly in your applications. Instead, you should always use the factory class to create the objects that you need in your application.

        The reason for this, is that the factory class is responsible for keeping track of each data object that you have access in memory. The factory class assures that each object of each entity class only exists in memory once.

        This is meant to avoid falling into inconsistencies when manipulating the information contained in each data object. For instance, if an application was manipulating two copies of the same data object, it would prevail only the changes made to the copy that was saved last, loosing the changes made to the copy saved first.

        The factory class is also responsible for managing other resources that may be needed to manipulate the data objects, like for instance establishing database connections.

        For each component there should be only one factory class. Since the factory class is a central place to manage common resources used by entity classes, only one factory class object should be created per execution of your application or each of your scripts.

        The presence of the factory class definition in your component is required. The factory class definition starts with the tag factory.

      • Factory functions
        • The component factory class has functions and variables that are always generated by Metastorage to perform base operations like factory initialization and finalization. Since these functions are always generated by Metastorage, you do not need to specify them on your component design.

          Besides the base functions, factory classes may also have several types of functions that depend on the needs of your applications. Therefore, each of the functions you need has to be defined in sections that start with the tag function.

          The definition of the factory functions is similar to entity classes functions. Only function type specific arguments vary.

          Here follows an overview of the available types of factory functions. For a more detailed explanation of the parameters of each type of function, please consult the factory function description section of the Metastorage reference manual.

        • createobject function
        • A createobject type function creates a new object of a given entity class from scratch.

        • custom function
        • A custom type function may be used to implement customizations of the factory class using arbitrary code written in the target language by the component designer.

        • finishtransaction function
        • A finishtransaction type function terminates a pending transaction started with a function of type starttransaction.

        • getobject function
        • A getobject type function retrieves an object of a given entity class from the persistence container, specifying as argument the respective OID.

        • getallobjects function
        • A getallobjects type function retrieves all objects of a given entity class from the persistence container. The objects that are retrieved may eventually be restricted to a subset that satisfies given filter condition specified as parameter.

        • starttransaction function
        • A starttransaction type function initiates a transaction. Transactions must be ended by calling a function of type finishtransaction.

        Here is an example of the definition of a factory class with several types of functions:

        
         <factory>
        
           <function>
             <name>createarticle</name>
             <type>createobject</type>
             <parameters>
               <class>article</class>
             </parameters>
           </function>
        
           <function>
             <name>getallarticles</name>
             <type>getallobjects</type>
             <parameters>
               <class>article</class>
             </parameters>
           </function>
        
           <function>
             <name>getarticle</name>
             <type>getobject</type>
             <parameters>
               <class>article</class>
               <id>
                 <argument>article</argument>
               </id>
             </parameters>
           </function>
        
           <function>
             <name>begin</name>
             <type>starttransaction</type>
           </function>
        
           <function>
             <name>end</name>
             <type>finishtransaction</type>
             <parameters>
               <commit>argument</commit>
               <argument>
                 <name>commit</name>
               </argument>
             </parameters>
           </function>
        
         </factory>
        
        

    • Object search filters
      • Object search filters are expressions that may be associated to functions that retrieve one or more objects to restrict the returned objects only to those that satisfy given conditions.

        The filter expressions are specified in the parameters section of factory functions of the type getobject or getallobjects and class functions of type getcollection.

        Since each object retrieval function may have be associated with a different filter expression, you can define as many functions as your applications need to perform different types of object searches.

        The syntax of filter expressions consists of tags and values that specify the filter operands and operators that are parsed by Metastorage when the respective component class function is processed. Metastorage generates a query condition clause that is used by the generated classes at runtime to specify which objects are retrieved.

        Metastorage supports many types of filter expression operand types and operators that are described in detail in Metastorage reference manual.

        Here is an example of how to define a factory function to retrieve all article objects that have the title set to Introduction.

        
         <function>
           <name>getIntroductionArticles</name>
           <type>getallobjects</type>
           <parameters>
        
             <class>article</class>
        
             <filter>
                 <variable>
                   <name>title</name>
                 </variable>
               <equalto />
                 <text>Introduction</text>
             </filter>
        
           </parameters>
         </function>
        
        

        If you expect that at most only one object of the queried class that satisfies your search condition will be found, you may also use a getobject type function. In this case, instead of the usual id function specific parameter, you just use the filter parameter.

        
         <function>
           <name>getIntroductionArticle</name>
           <type>getobject</type>
           <parameters>
        
             <class>article</class>
        
             <filter>
                 <variable>
                   <name>title</name>
                 </variable>
               <equalto />
                 <text>Introduction</text>
             </filter>
        
           </parameters>
         </function>
        
        

        If you need to use object search parameters that are only defined when your application is run, you can define custom function arguments to pass additional search parameters. Then you use the argument operand in your filter expression to refer to the values passed as arguments to the function at runtime.

        
         <function>
           <name>getArticleByTitle</name>
           <type>getobject</type>
        
           <argument>
            <name>title</name>
            <type>text</type>
           </argument>
        
           <parameters>
        
             <class>article</class>
        
             <filter>
                 <variable>
                   <name>title</name>
                 </variable>
               <equalto />
                 <argument>title</argument>
             </filter>
        
           </parameters>
         </function>
        
        

        If you need specify complex search filter conditions that involve multiple related objects, you need to use the object operand type to declare any objects involved in the relationships.

        Here is an example of a function that uses a more complex filter expression to retrieve a collection articles of a given category class that have a given title and whose associated author has a give name.

        
         <function>
           <name>getArticlesByTitleAndAuthor</name>
           <type>getcollection</type>
        
           <argument>
            <name>title</name>
            <type>text</type>
           </argument>
        
           <argument>
            <name>author</name>
            <type>text</type>
           </argument>
        
           <parameters>
        
             <collection>articles</collection>
        
             <filter>
        
                 <variable>
                  <name>title</name>
                  <object>article</object>
                 </variable>
                <equalto />
                 <argument>title</argument>
        
               <and />
                <object>
                 <name>author</name>
                 <class>writer</class>
                </object>
               <equalto />
                <variable>
                 <object>article</object>
                 <name>author</name>
                </variable>
        
              <and />
                <variable>
                 <object>author</object>
                 <name>name</name>
                </variable>
               <equalto />
                <argument>author</argument>
        
            </filter>
        
           </parameters>
         </function>
        
        

    • Schema management class
      • The schema management class is a special class that is meant to assist in the installation of the database schema. It is capable of creating all the tables that are necessary to hold the information of your entity class objects.

        Currently, the schema management class is very simple. It just has a few configuration variables. It must have one function of type installschema that has to be declared like the functions of the other types of classes.

        The declaration of the schema class is in a section of the component definition that starts with the tag schema. Here is how to declare the schema class with the installschema function:

        
         <schema>
        
           <function>
             <name>installschema</name>
             <type>installschema</type>
           </function>
        
         </schema>
        
        

    • Form handling classes
    • This section is not yet written.

    • Report classes
    • This section is not yet written.


  • Generating component code
  • To generate the component code, you need to execute Metastorage application. This application can be executed from the command line shell or via a Web interface. Both ways are explained in detail in the Metastorage reference manual.

    To help you making the first experiments generating code with Metastorage, the example component definition presented in the Metastorage documentation is provided in a file named cms.component located inside the projects/cms/ directory.

  • Generating UML class diagrams
  • Metastorage generates class diagrams in UML notation every time a component is compiled.

    You can specify the directory where the diagram file when the component file is compiled. The name of the class diagram file is the name of the component declared in its definition followed by the extension .dot. For instance, if the component name is cms, the diagram file name is cms.dot.

    The diagram file is generated in the DOT format. This is the format for defining graphs that is expected by the Graphviz program as input format. Graphviz is an open source program developed by AT&T Research Labs.

    A UML class diagram file, like any graph file in the DOT format, can be rendered in many known output formats like: GIF, JPEG, Postscript, PDF, etc..

    To render a class diagram in any of the supported formats, you need to download and install the Graphviz software package. Then you use the program named dot from the command line passing as arguments the name of the class diagram DOT file, the output format and the output file name.

    For instance, if you want to render the generated class diagram of the cms component in the GIF format, you can do it by running a command line like this. The class diagram is rendered to the GIF file named cms.gif.

    
     dot cms.dot -Tgif -o cms.gif
    
    

  • Using the generated code
    • Installing the database schema
    • The first thing that should be done before starting to use the code generated by Metastorage in your applications, is to install the support database schema where the application objects will be stored.

      For that, Metastorage generates a database schema definition in a XML schema format that is database independent. It also generates a class that is capable of installing that schema definition.

      You just need to set a few configuration variables of that class and call one function that installs the database schema according to the configuration definitions.

      • Configuring the database setup
      • The key installation files that Metastorage generates, the schema definition and the schema installation class, are created right in the install directory.

        They have file names based on the component name. For instance, if the component name is cms, the schema definition is named cms.schema and the schema installation class file is named cmsschema.php.

        Additionally, Metastorage also generates scripts to use these installation files. If you prefer, you may ignore these scripts and create your own. But if you want to get started quickly, you may find them in the setup sub-directory inside the install directory.

        There are two setup scripts. The install.php script creates an object of schema installation class, sets a few variables with configuration values and calls the class to install the schema.

        The global_options.php includes definitions of application configuration default values. These values are used not only by the schema setup script, but also by other scripts that are used to access data object classes.

        Currently, the configuration script defines the values of database configuration and debugging options. In the future, this script may contain other types of configuration values.

        Here is an example of the generated configuration script global_options.php:

        <?php
        
         $global_options=array(
           "database_connection"=>"mysql://localhost/cms",
           "database_name"=>"cms",
           "create_database"=>1,
          "debug"=>0
         );
        
        ?>

        As you may see, this configuration script consists of the definition of an associative array variable named $global_options. The indexes of the array entries define the name of the configuration options. The values of the array entries specify the default values of the respective options.

        Here follows the meaning of each of the current options defined in this configuration script:

        • database_connection - database connection string
        • It identifies the parameters of connection to the database. Its format is similar to an URL:

          
           Type://User:Pass@Host:Port/Database?Options/opt1=val&Options/opt2=val2
          
          

          For instance, if you want to use a MySQL database named cms using the INNODB table type to support transactions, you need to set the connection string like this:

          
           mysql://localhost/cms?Options/DefaultTableType=INNODB&Options/UseTransactions=1
          
          

          Please read more about this format in the documentation of Metabase.

        • database_name - name of the database on which the schema is to be installed
        • create_database - boolean option that determines whether the installation class should create a new database when the schema is being installed for the first time
        • Usually this option is set to 1, but some types of database do not permit the creation of databases from application scripts. In this case, this option value should be set to 0 and it should be the responsibility of the database administrator to create the database before installing the schema with this installation script.

          This option should also be set to 0 if you want to use a database that is already created, possibly installed by the setup script of another component.

        • debug - boolean option that determines whether the database access classes should capture debugging information for posterior analysis in case of any problem occurs

        Since Metastorage will overwrite the global_options.php configuration script every time it regenerates the files of a project, you should not edit this script to change the options values.

        Instead, you can create another script named local_options.php also in the setup directory to redefine the values of the options for which the default values are not satisfactory. The install.php script will include local_options.php script if it exists.

        Here is an example of a custom configuration script local_options.php:

        <?php
        
         $global_options["database_connection"]=
           "pgsql://localhost/cms";
         $global_options["debug"]=1;
        
        ?>

      • Executing the schema installation
      • Once you have customized the configuration options to work with your database setup, you just need to execute the install.php script to install your database schema.

        Since installing a database schema is not a task that requires a Web server, although it could be executed as a normal PHP Web page script, it is recommended that you run the install.php script from your operating system shell command line using PHP CLI or CGI executable program versions.

        All you need to do is to change the current directory to install/setup and enter this shell command line:

        
         php -q install.php
        
        

      • Updating the installed schema
      • When you install your component database schema for the first time, you may notice that a new file with the extension .installed is created inside the install directory. That file is a copy of the just installed database schema definition file. It is meant to keep track of the version of the schema that is currently installed.

        If you have made any changes in your component that lead to a new database schema definition, you just need to repeat the same procedure of the first time schema installation.

        The schema installation class is capable of updating your schema without destroying the data objects added to the database since it was installed for the first time or updated for the last time. Still, it is always recommended to backup your database before updating its schema.

        The schema installation class will determine whether the schema is being installed for the first time or is just updating an already installed schema. That is done by looking for the last install schema copy file with the .installed extension.

        Therefore, it is very important that you do not remove the .installed schema definition copy file or else the schema updating procedure will fail.

      • List of files involved in the installation
      • Below follows the list of files and directories involved in the installation of the schema of a database to store the objects of an example component named cms:

        
         +- install/ -+- cms.schema
                      +- cms.schema.installed
                      +- cmsschema.php
                      +- setup/ -+- install.php
                                 +- global_options.php
                                 +- local_options.php
        
        

    • Setting up the object factory
    • Once you have the database schema installed or updated, you can start writing your application scripts. Every script that needs to create or manipulate data objects, needs to setup an object of the factory class first.

      The factory class is responsible for keeping in a central place all the information related to the resources that are necessary to access to the data objects in a organized and efficient manner. For instance, it is responsible for setting up database connections, keeping track of all data objects that are created or retrieved by a script, handling errors, logging, etc..

      Each script should create only one object of the factory class. After setting some of the object variables with configuration values, the scripts should call the initialize function. This function is responsible mainly for setting up the database connection according to the configuration values specified in the respective factory object variables.

      Once the factory object is successfully initialized, your script may start accessing your application data objects. When the script is done, it should call the factory object finalize function, so the class can finish any pending actions and close the database connection that was previously setup.

      Below follows an example of a typical application script with the factory object initialization and finalization.

      The paths used to include the auxiliary scripts and class files assume that this example script is running in sub-directory of the install directory.

      You may use this example script as template for your application scripts. If your application scripts are actually serving Web pages, the messages that are outputted would eventually include HTML tags, which were suppressed in this script just to simplify.

      <?php
      
        /*
         *  First load the application configuration option values
         */
        require("../setup/global_options.php");
        if(file_exists('../setup/local_options.php'))
          include('../setup/local_options.php');
      
        /*
         *  Include the database access libraries
         */
        require("../metabase/metabase_interface.php");
        require("../metabase/metabase_database.php");
      
        /*
         *  Include the factory class
         *  Here you may also include the data object classes that
         *  may be needed
         */
        require("../cms.php");
      
        /*
         *  Create the factory class object
         */
        $factory = &new cmsclass;
      
        /*
         *  Set the factory object variables with configuration
         *  values
         */
        $factory->connection =
          $global_options["database_connection"];
        $factory->debug = $global_options["debug"];
        $factory->includepath = "../metabase";
      
        /*
         *  Initialize the factory object to setup the database
         *  connection
         */
        if($factory->initialize())
        {
          /*
           *  If it succeeded, your script action starts here
           */
      
          /*
           *  ...
           */
      
          /*
           *  When you are done with your script, finalize the
           *  factory object to make it end any pending actions,
           *  like closing the database connection setup
           */
          $factory->finalize();
        }
      
        /*
         *  Was there an error?
         */
        $error = $factory->error;
        if(strlen($error)!=0)
        {
          /*
           *  If so, output an user friendly error message
           */
          echo "Sorry, the system is not available now.\n";
      
          /*
           *  ... and maybe additional error information to help
           *  you debugging the problem.
           */
          if($global_options["debug"])
            echo "Error: ", $error, "\n";
        }
      ?>

    • Starting and finishing transactions
    • When scripts of a multi-user application access the same persistent objects at the same time, that may lead to inconsistency problems caused by scripts that attempt to update information that was already changed by concurrent scripts.

      To avoid inconsistency problems caused by concurrency issues, the sections of each script that access to persistent objects should be involved within a transaction.

      Transactions can transparently make each script access the stored objects as if there is never more than one script changing the persistent objects at the same time.

      A transaction must be started with a factory class function of type starttransaction and ended with a function of type finishtransaction.

      If all operations within a transaction succeed, the transaction should be committed. Otherwise, the transaction should be aborted so the changes are undone with an operation usually known as transaction rollback.

      The finishtransaction type functions can either commit or rollback a transaction within the same function call.

      
        /*
         *  Initialize the factory object to setup the database
         *  connection
         */
        if($factory->initialize())
        {
          /*
           *  If it succeeded, start a transaction before accessing the objects
           */
          if($factory->begin())
          {
      
            /*
             *  Access your objects
             */
            $success = $some_success_condition;
      
            /*
             *  Commit the object changes if it succeed
             *  or rollback the transaction otherwise
             */
            $this->end($success);
          }
      
          /*
           *  When you are done with your script, finalize the
           *  factory object to make it end any pending actions,
           *  like closing the database connection setup
           */
          $factory->finalize();
        }
      
        if(!$success)
        {
          /*
           * Do the usual error handling.
           */
        }
      

    • Creating new a data objects from scratch
    • Creating a new data object is just a matter of creating an object of the respective class, set its variables with the initial values and store the object permanently.

      The important detail is that you should never create an object of a data class directly. Instead, you should use a function of the factory class of type createobject previously declared in your component factory class definition to create an object of the class of the data object that you want to create.

      This is intended to let the factory object keep track of all the data objects that your script creates or retrieves from the database, and so avoid keeping multiple inconsistent copies of the same data object in memory.

      The createobject type function declaration must specify the class of the object that you want to create. Therefore, if you want to create objects of different classes, you need to declare a createobject type function for each of the classes.

    • Storing object changes
    • Once you have created a data object and set its variable values, you need to store the object in the database to make the object available to the whole application.

      Storing a data object persistently is done by calling a function of the data object class of type persist. That function should be explicitly declared in the component data object class definition.

      When a persist type function is called on a just created object, it automatically creates and assigns a new object identifier (OID) number. That number will be used as unique key to locate the object in the database table on which it will be stored.

      If the persist function successfully stores the object in the database, it will assign an implicit object variable named id with the OID value. From then on, that value should be used to pass to other functions that take an OID as parameter of this type of class.

      Once you have stored the object in the database, you may continue changing its variables according to your application needs. You should also use the persist type function to store the object changes.

      Below follows an example of a script that creates a new object of the class writer, initializes its variable values and store the object persistently in the database.

      /*
       *  Call the factory to create a new writer object
       */
      $writer = &$factory->createwriter();
      
      /*
       *  If it failed, it returns a null object reference
       *  The IsSet() function determines whether the object
       *  reference is not null
       */
      
      $success = IsSet($writer);
      if($success)
      {
        /*
         *  If it succeeded, initialize the writer object
         *  variables.
         */
        $writer->name = "John Doe";
        $writer->email = "john@doe.com";
      
        /*
         * Store the object in the database.
         */
        $success = $writer->persistwriter();
        if($success)
        {
          /*
           * If it succeeded, output a success message.
           */
          echo "The new writer was registered with the",
            " identifier ", $writer->id, "\n";
        }
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      

    • Validating object information
    • Before storing the a new object calling a persist type function, it is convenient to verify whether the new values respect the validation rules.

      Validating new object variable values is strongly recommended as it is the way to enforce application logic rules. It may also help preventing security exploits from users with malicious intentions that may enter compromising values, for instance in the forms that serve as your application user interface.

      Validating the information of an object can be done by calling a function of the data object class of type validate. That function should be explicitly declared in the component data object class definition.

      A validate type function takes as argument a reference to a variable on which it will be returned an error code integer number that indicates which of the object validation rules was not satisfied. The error codes are specified in the definition of each validation rule of a class in the component definition file.

      If all validation rules are satisfied, the validate type function returns 0. If one or more validation rules are not satisfied, the function returns the error code associated with first unsatisfied rule.

      Some validation rules may require accessing to the database, like for instance the unique rule that requires that a given set of variables has unique values across all objects of that class.

      Since database access may be subject of failure under certain circumstances, like for instance when the database is down, a validate type function returns a boolean value that indicates whether the validation could be performed without problems, even when any of the validation rules could not be satisfied.

      Below follows an example of a script that creates a new object of the class article, initializes its variable values, validates the object information, and if all goes well, it stores the object persistently in the database.

      /*
       *  Call the factory to create a new article object
       */
      $article = &$factory->createwriter();
      
      /*
       *  If it failed, it returns a null object reference
       *  The IsSet() function determines whether the object
       *  reference is not null
       */
      
      $success = IsSet($article);
      if($success)
      {
        /*
         *  If it succeeded, initialize the article object
         *  variables, in this case with values taken from a form
         *  being submitted.
         */
        $article->title = (IsSet($_POST["title"]) ? $_POST["title"] : "");
        $article->lead = (IsSet($_POST["lead"]) ? $_POST["lead"] : "");
        $article->body = (IsSet($_POST["body"]) ? $_POST["body"] : "");
      
        /*
         *  Validate the article object information.
         */
        $success = $article->validatearticle($error_code);
        if($success)
        {
          /*
           *  The validation was performed without problems.
           *  Verify whether all validation rules were satisfied.
           */
          switch($error_code)
          {
            case 0:
              /*
               *  All validation rules were satisfied. Store the
               *  object in the database.
               */
              $success = $article->persistarticle();
      
              if($success)
              {
                /*
                 * If it succeeded, output a success message.
                 */
                echo "The new article was submitted with the",
                     " identifier ", $article->id, "\n";
              }
              break;
      
              /*
               *  At least one validation rule was not satisfied.
               *  Output an user friendly error message.
               */
            case 1:
              echo "<p>It was specified an empty article title.</p>";
              break;
            case 2:
              echo "<p>There is already an article with the same title.</p>";
              break;
            case 3:
              echo "<p>It was specified an empty article lead text.</p>";
              break;
            case 4:
              echo "<p>It was specified an empty article body text.</p>";
              break;
          }
        }
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      

    • Retrieving individual objects
    • Once an object is stored in the database, any of your application scripts can retrieve it by calling a factory function of type getobject. Such function must be declared in your component factory class definition.

      The getobject type function declaration must specify the class of the object that you want to retrieve. Therefore, if you want to get individual objects of different classes, you need to declare a getobject type function for each of the classes.

      A getobject type function takes as argument the OID of the object that you want to retrieve. So, some how you need to already know the OID of that object. If you are developing a Web application, you may obtain the OID by passing it as one of the page URL query arguments by the means of a link from another page or maybe as an hidden field of a form submitted from another page.

      If the specified OID does not exist, the getobject function returns a null object. If there was an error, the factory object variable name error is set to the resulting error message. You need to check this variable to determine whether the getobject function returned a null because the specified object does not exist or because there was an unexpected runtime error.

      Below follows an example of a page script that retrieves an object given its OID passed to this script by the means of a page URL query argument.

      If the script was named get_writer.php and the OID of the writer object that you wanted to get via a page URL query argument named writer is 1, the request URL would be like get_writer.php?writer=1.

      /*
       *  Make sure that the writer query argument was really
       *  passed to this script
       */
      
      if(IsSet($_GET["writer"])
      {
        /*
         * Retrieve the OID from the page URL query argument
         * Keep in mind that that an URL query argument is a
         * string. It needs to be converted to an integer which is
         * the correct type of an OID. Converting with the intval()
         * function also prevents security exploits, as any strings
         * with invalid numbers are converted to 0. Usually OID are
         * sequential numbers that start at 1.
         */
        $writer_id=intval($_GET["writer"]);
      
        /*
         *  Call the factory to get the writer object
         */
        $writer = &$factory->getwriter($writer_id);
      
        /*
         *  If it failed, it returns a null object reference.
         *  The IsSet() function determines whether the object
         *  reference is not null.
         */
      
        $success = IsSet($writer);
        if($success)
        {
          /*
           * If it succeeded, do something with the object, like
           * displaying its information. Make sure that any text
           * values displayed in HTML pages are properly escaped
           * with the HtmlSpecialChars() function to prevent cross
           * site scripting security exploits.
           */
          echo "<p>The writer with the identifier ", $writer->id,
            "is named ",HtmlSpecialChars($writer->name),"</p>\n";
          echo "<p>His e-mail address is ",
            HtmlSpecialChars($writer->email), "</p>\n";
        }
        else
        {
          /*
           * Did it fail but there was no error
           */
          if(strlen($factory->error)==0)
          {
            /*
             * If so, the query run successfully but the specified
             * writer object was not found.
             */
            $success=1;
            echo "<p>It was not specified the identifier of an ",
              "existing writer!</p>";
          }
        }
      }
      else
      {
        /*
         * There was no error but nothing happened because the
         * writer OID was invalid.
         */
        $success=1;
        echo "<p>It was not specified a valid writer",
          "identifier!</p>";
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      

    • Retrieving all objects of a class
    • When you do not know the identification of the objects of a class that are stored in the database, you can first query all the objects of that class and then retrieve the objects that you are interested given their OID numbers that were obtained first.

      You can retrieve all the objects of a given class by calling a factory class function of type getallobjects. Such type of functions must be declared in your component factory class definition for each of the classes that you intend to retrieve all objects in your application.

      The getallobjects type of function returns an array of references to all the objects of the specified class. If at the moment there are no objects of the specified class stored in the database, the returned array will be empty. If there was an error, the function will return a null array reference.

      Below follows an example of a page script that retrieves all objects of a class named writer and outputs links to another page that outputs detailed information about individual writer objects.

      /*
       *  Call the factory to get all writer objects
       */
      $writers = &$factory->getallwriters();
      
      /*
       *  If it failed, it returns a null array reference
       *  The IsSet() function determines whether the array
       *  reference is not null.
       */
      
      $success = IsSet($writers);
      if($success)
      {
        /*
         * If it succeeded check if it returned any writer objects
         */
        $total_writers = count($writers);
        if($total_writers)
        {
          /*
           * Display links to pages that display more detailed
           * information about the listed writers.
           */
          for($writer=0; $writer<$total_writers; $writer++)
          {
            echo "<p><a href=\"get_writer.php?writer=",
              $writers[$writer]->id, "\">",
              HtmlSpecialChars($writers[$writer]->name),
              "</a></p>\n";
          }
        }
        else
        {
          /*
           * No writers? Display an user friendly message telling
           * that.
           */
          echo "<p>There are currently ",
            "no registered writers.</p>\n";
        }
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      

    • Retrieving only objects that satisfy a condition
    • Using functions that retrieve only objects that satisfy a given condition specified by filter expression, is the same as using functions that do not apply any object search filters.

      The only special case is when the functions take custom arguments that are used to specify the values of parameters used in the filter expression. In this case it is a matter of passing the additional arguments in the function call by the order of which they are specified in the function definition.

    • Creating copies of existing objects
    • Some applications need to create new versions of a persistent object. You can create a new object and assign the variables of the new object with the values of another object.

      However, that may be a tedious task, especially when you need to copy objects with many variables. Alternatively, you can use a function of the type cloneobject which does precisely that.

      cloneobject functions create a new object but do not store it. You need to call a function of type persist when the object copy is ready to be saved.

    • Deleting an object permanently
    • Once an object is stored persistently in the database, it may be removed permanently by calling a function of the type delete declared in the component definition of the respective class.

      If the object to be deleted was not yet stored in the database, the delete type function will just remove any references to the object stored in memory.

      If the object was already stored in the database and it is successfully removed, the call to the delete function returns true.

      Keep in mind that due to PHP delayed garbage collection, there may be other references to the deleted object in memory. Although this may not affect the normal execution of your application scripts, you may want to force the removal of the object from memory by calling the PHP Unset function by passing any variables that you may have used to store references to the object in your script after calling the delete type function.

    • Storing files in large data variables
    • A large data variable may be set to the contents of a string variable or to the contents of a file. In either way assigning a large data variable is done using a function of type setlargedata.

      setlargedata functions may take as argument the string of data or the name of the file with the contents to be assigned to the large data variable.

      The contents of a large data variable are only effectively assigned when a persist type function of the object class is called.

      Below follows an example of a script that assigns the picture large data variable of class writer, with a file uploaded using a Web form.

      This is the form HTML:

      <form method="post" action="upload_picture.php" enctype="multipart/form-data">
      <input type="file" name="picture" accept="image/jpeg" /><br />
      <input type="submit" value="Upload" />
      <input type="hidden" name="writer" value="1" />
      </form>
      

      This is the form handling script upload_picture.php:

      
      /*
       *  Make sure form was submitted correctly
       */
      
      if(IsSet($_POST["writer"])
      && IsSet($_FILES["picture"])
      && is_uploaded_file($_FILES["picture"]["tmp_name"]))
      {
        /*
         * Retrieve the OID from the posted form values
         * Sanitize the writer OID value using the
         * intval() function to prevent security exploits
         */
        $writer_id=intval($_GET["writer"]);
      
        /*
         *  Call the factory to get the writer object
         */
        $writer = &$factory->getwriter($writer_id);
      
        /*
         *  If it failed, it returns a null object reference.
         *  The IsSet() function determines whether the object
         *  reference is not null.
         */
      
        $success = IsSet($writer);
        if($success)
        {
          /*
           * If it succeeded, assign the writer object picture variable
           * to the contents of the uploaded file.
           */
          $writer->setpicture($_FILES["picture"]["tmp_name"]);
      
          /*
           * Other variables may be assigned at the same time.
           * Additional validation of the file type and length
           * should have been done here.
           */
          $writer->picture_type="image/jpeg";
      
          /*
           * Then save the updated object.
           */
          $success=$writer->persistwriter();
          if($success)
          {
            /*
             * If it succeeded, show a success message to the user.
             */
            echo "<p>The picture of the writer ",
              HtmlSpecialChars($writer->name),
              " was updated successfully</p>\n";
          }
        }
        else
        {
          /*
           * Did it fail but there was no error
           */
          if(strlen($factory->error)==0)
          {
            /*
             * If so, the query run successfully but the specified
             * writer object was not found.
             */
            $success=1;
            echo "<p>It was not specified the identifier of an ",
              "existing writer!</p>";
          }
        }
      }
      else
      {
        /*
         *  The form was not submitted correctly.
         *  Show an error message to the user.
         */
        $success=1;
        echo "<p>The form was not submitted correctly!</p>";
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      
      

    • Serving large data variables like files
    • The contents of a large data variable may be retrieved into a string variable, into a file, or served as part of the current script output. In either way retrieving a large data variable is done using a function of type getlargedata.

      getlargedata functions may take as argument a reference to a string variable, or the name of the file into which the the contents of the large data variable are retrieved. Serving the contents of a variable as part of the current script output does not take additional arguments.

      If the variable value is optional, its contents may not be defined when the variable is accessed. To know whether the variable is undefined, an additional argument may be passed with a reference to a boolean variable that is set by the getlargedata function. If that boolean variable is set to 1, the large data variable is defined and its contents were retrieved. Otherwise, the large data variable value is undefined.

      Below follows an example of a script that serves to the user browser the contents of the picture large data variable of an object of the class writer. The OID of the writer object is passed to the script as query argument.

      
      /*
       *  Make sure that the writer query argument was really
       *  passed to this script
       */
      
      if(IsSet($_GET["writer"]))
      {
        /*
         * Retrieve the OID from the query argument value
         * Sanitize the writer OID value using the
         * intval() function to prevent security exploits
         */
        $writer_id=intval($_GET["writer"]);
      
        /*
         *  Call the factory to get the writer object
         */
        $writer = &$factory->getwriter($writer_id);
      
        /*
         *  If it failed, it returns a null object reference.
         *  The IsSet() function determines whether the object
         *  reference is not null.
         */
      
        $success = IsSet($writer);
        if($success)
        {
          /*
           * If it succeeded, check if the picture type was already set to
           * figure whether a picture was previous stored in the variable
           */
      
          if(IsSet($writer->picture_type))
          {
            /*
             * Output the content type header so the browser displays
             * the variable contents properly
             */
            Header("Content-Type: ".$writer->picture_type);
      
            /*
             * Now output the stored picture contents
             */
            $success = $writer->outputpicture($undefined);
      
          }
          else
          {
            /*
             * No picture? Lets serve a default image from a file
             */
            Header("Content-Type: image/jpeg");
            readfile("default.jpeg");
          }
        }
        else
        {
          /*
           * Did it fail but there was no error
           */
          if(strlen($factory->error)==0)
          {
            /*
             * If so, the query run successfully but the specified
             * writer object was not found.
             */
            $success=1;
            echo "<p>It was not specified the identifier of an ",
              "existing writer!</p>";
          }
        }
      }
      else
      {
        /*
         * There was no error but nothing happened because the
         * writer OID was invalid.
         */
        $success=1;
        echo "<p>It was not specified a valid writer",
          "identifier!</p>";
      }
      if(!$success)
      {
        /*
         * Do the usual error handling.
         */
      }
      
      

    • Establishing relationships with reference objects
    • One way to establish a relationship between two objects is to use reference variables. These are like the common type variables, except that they store the OID of another object that is referenced this way.

      To set or retrieve an object associated with a reference variable of a given class, you need to use functions of the type setreference and getreference respectively. A function of any of these types should be the declared in the respective component class definition and be associated to a specified reference variable.

      To access multiple reference variables that may exist in the same class, there should be distinct getreference and setreference functions.

      The setreference type function takes an object of the class specified in the reference variable declaration and stores the respective OID. If you want to set the reference variable to no object, just pass a null object reference as argument of the function.

      Keep in mind that any changes made to a reference variable or any other class variable, are only made permanent by saving the object to the database using a persist type function.

      Note also that if it is passed to a setreference type function a reference object that was just created and it was not yet saved, the function will fail because the object's OID is only determined when its persist type function is called for the first time.

      The getreference type function does the opposite of setreference. It returns the object identified by the OID stored in a reference variable.

      If the reference variable is set to null OID, the function returns a null object reference. If there was an error the function also returns null. Therefore, you need to check the factory class error variable to determine whether it returned a null object reference or there was some runtime error.

    • Managing collections of related objects
    • Another way to establish a relationship between two objects is through collections. A collection establishes a relationship between an object of one class and a set of objects of another class.

      To manage collections of related objects there are a few types of functions that can add or remove an object from a collection and also retrieve objects that belong to the collection.

      Although Metastorage makes transparent for the applications the manipulation any of the types of collections, it is important to understand that there are two types of relationships between objects that are implemented through collections: 1-to-many relationships and many-to-many relationships.

      In 1-to-many relationships, the objects of one class that belong to a collection have a reference variable that stores the OID of the the object the other class that defines the collection.

      In many-to-many relationships Metastorage creates an additional database table that stores the OID of the objects of the related classes.

      To manipulate either of the types of relationships is a just matter accessing the tables that store the OID of the related objects.

      • Adding an object to a collection
      • To add an object to a collection you need to call a function of type addtocollection. That function should have been declared in the definition of the class of the object that has the collection.

        Such function takes as argument a reference to the object that is meant to be added to the collection. If the object is successfully added, the function returns true.

        Notice that when you call a function to add an object to a collection that establishes a one-to-many relationship, this is same as setting the reference variable of the object to be added with the OID of the object that has the collection.

        With one-to-many relationships relationship calling an addtocollection type function is equivalent to calling a setreference type function of the class of object that is added to the collection.

        This means that after calling the addtocollection type function, you still need to call a persist type function of the class of the object that was added to make permanent the relationship that was established this way between the two objects.

      • Removing an object from a collection
      • Adding and removing an object from a collection are very similar procedures. The only difference is that you need to call a function of the class that has the collection of the type removefromcollection instead of addtocollection.

        If you remove an object from a collection that establishes a one-to-many relationship, the collection reference variable of the object that was removed is set to a null OID value.

        You also need to call the persist type function of the class of the removed object to make permanent the removal of the object from a collection that establishes a one-to-many relationship.

      • Retrieving objects from a collection
      • Retrieving objects from a collection is similar to retrieving all objects from a class. The only difference is that instead of calling a factory class function of type getallobjects, you need to call getcollection type function of the class that has the collection.

        If the getcollection function is defined specifying an object filter expression that uses custom function arguments, the additional arguments have to be passed to the function call by the order they are specified in the function definition.


For more information contact: info-at-meta-language.net