Using the atkMetaNode

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Using the atkMetaNode

Complexity: Intermediate
Author: Peter C. Verhage <peter@achievo.org>

List of other Howto's

Contents

Preface

One of the great strengths of ATK is the small amount of code you need to accomplish various tasks. The last few months there have been some new developments which will make it possible to use even less code. This howto is a quick introduction to the atkMetaNode which makes it possible to write a node in just a single line of code. Certain parts of this howto require some insight in the way normal atkNode's work, but for the most part no internal ATK knowledge is needed.

How it works

The atkMetaNode uses meta data readily available in the database to determine of which attributes a node should consist of, which type these attributes should have, what their flags should be etc. Most of the time the atkMetaNode is quite able to determine things on it's own, but sometimes you must give some hints to make things work the way you want. After the node construction based on the database meta data is done you can just handle the node as any other ordinary node. This means you can even add your own attributes to the node, like you would normally do, but most of the times this won't be needed.

Show me the way

So you would like to know how to use the atkMetaNode in practice? Well here it comes. If our database would contain a table called country which consists of the following columns:

 Name           Type             Extra
 -------------- ---------------- ---------------------------------
 country_id     number           not null, primary key, sequence
 iso_code       varchar(2)       not null, unique
 name           varchar(100)     not null

then creating a (meta) node for the country table is simply a matter of creating a file called class.country.inc in your ATK module which consists of the following code:

 class Country extends atkMetaNode {}

That's it! If you would call the admin action on the country node in your browser you will see that you have a fully functional node for managing countries. The node will consist of two fields, one for entering the iso code and one for entering the country name. Both fields will be mandatory and ATK will check if you enter a unique value in the iso code field. If you add a new country the country_id field will automatically be filled with the next sequence value. The country_id field is hidden because ATK knows this is an auto incrementing primary key field.

Now, let's create a second table, called contact, consisting of the following columns:

 Name           Type             Extra
 -------------- ---------------- ---------------------------------
 contact_id     number           not null, primary key, sequence
 email          varchar(100)     not null, unique
 firstname      varchar(50)           
 lastname       varchar(50)     
 address        varchar(100)     
 housenumber    number(6)
 suffix         varchar(4)
 zipcode        varchar(10)     
 city           varchar(100)
 birthdate      date
 country_id     number           references country(country_id)


Initially we will also create a meta node for this table in just a single line of code, just like before:

class Contact extends atkMetaNode {}


But if we do this, you will see you just get a simple country_id field, in which a user can enter the numerical identifier of a certain country. Not that user friendly, is it? First of all a user needs to know what country identifiers are valid and secondly what identifier belongs to which country. We can do better than that, let's use a many-to-one relation. Change the contact node code to the following:

  class Contact extends atkMetaNode
  {
    public static function meta($policy)
    {
      $policy->hasOne("country");
    }
  }

Now the user will be presented with a list of countries, but unfortunately this list still consists of only the numerical identifier for the countries. There are two ways to solve this problem. First of all you could create a method called descriptor_def in the country node which returns a descriptor template like [name]. But what if you don't have access to this node (because another user has written it), or what if you want to display both the iso code and name instead of just the name (which is currently defined in the descriptor_def method)? Simply add a second parameter to the call to the hasOne method in which we define the descriptor template:

  class Contact extends atkMetaNode
  {
    public static function meta($policy)
    {
      $policy->hasOne("country", "[name] ([iso_code])");
    }
  }

That looks more like it! But what did we really do here? Well first of all we've implemented the special meta method. This method is called from within the atkMetaNode's constructor and is supplied with the so called meta policy. The meta policy performs the real magic for the atkMetaNode; mapping table columns to attributes, adding relations to the node with a minimal amount of hinting etc. The best thing is, if you don't like the current detection scheme of the default meta policy, you can simply subclass it and create your own policy. Inside the meta method you can call some methods on the policy used for the node to give some hints. In the example above we hint the policy that we have a relation with the country node. The policy then determines if this is a one-to-one or many-to-one relation, what columns need to be used etc. Make sure you always declare your meta method as "public static", this way you ensure that you don't (by accident) use $this inside your meta method and this also assures that the automatic caching mechanism kicks in (more on this later).

Ofcourse there are more hinting methods you can use. Let's give an example:

  class Contact extends atkMetaNode
  {
    public static function meta($policy)
    {
      $policy->hasOne("country", "[name] ([iso_code])");
      $policy->setIncludes("contact_id", "email", "firstname", "lastname", "city");
      $policy->addFlags("contact_id", AF_AUTOKEY);
      $policy->addFlags("firstname", "city", AF_OBLIGATORY);     
    }
  }


The setIncludes method can be used to specify the only columns this node should consist of. In the example above we told the policy to only include the contact_id, email, firstname, lastname and city columns. We can also specify the opposite by calling the setExcludes method, as you might have guessed this method can be used to specify a list of columns that should not be used by the node. The setIncludes method also determines (by default) the order of the attributes. Normally the meta node uses the order of the table columns. But you can use the hint method setOrder to specify another order. The setIncludes method calls the setOrder method by default, unless you specify FALSE as the last parameter.

The addFlags (and also the addFlag) method can be used to specify additional flags for attributes. The example above shows that you can call this method specifying one or more column/attribute names and a flag (or a combination of flags using the bitwise AND operator). Next to the addFlags method there is also a method to remove flags (removeFlags) and a method to set the flags (setFlags) instead of adding the flags.

Another hint method is the setTabs (or setTab). As you might have guessed you can use this method to specify the tabs on which one or more attributes should be shown.

We've already seen the hasOne hint method which can be used for both many-to-one and one-to-one relations. But there also exists a hasMany method if you want to add a one-to-many relation, this method works the same as the hasOne method. A nice feature of the relation hinting methods is that they try to find the destination node using different names based on the supplied accessor name. If we would like to show the contacts in a certain country we could supply the meta policy for the country node with the following hint:

  class Country extends atkMetaNode
  {
    public static function meta($policy)
    {
      $policy->hasMany("contacts", "[email]");
    } 
  }


As you can see we did not specify a module name. The atkMetaPolicy will first try to find a node matching the given name inside the module of the source node. If no such node exists, the policy will check if the node exists outside the module (in the main application). Most of the times this should suffice. But if your node exists in another module you need to specify the module using the normal "module." prefix. More careful readers might have noticed that I used the plural form of person for the accessor name. The reason you can do this is that the policy knows how to convert the plural form to the singular form. If the policy can't find the destination node using the given accessor name it will try to convert the name to the singular form for has-many relations and to the plural form for has-one relations. This makes adding relations more natural.

Sometimes you may want (or need) to use a class name for your node that differs from the table name. If this happens to be the case you could override the constructor of your node and specify the table name as one of the parameters to the super constructor (see the ATK PHPdocs). But fortunately there is a nicer way to do this, just add an instance variable named $table to your class and give it as value the table name you want to use:

  class Country extends atkMetaNode
  {
    protected $table = "lesson6_country";
  
    public static function meta($policy)
    {
      $policy->hasMany("contacts", "[email]");
    } 
  }

There are other variables you can use to specify a different meta policy, a different sequence name etc. For now, just look at the sources of the atkMetaNode class.

Caching

The meta node uses the database metadata to build the node. To prevent the node from doing this every single request we've added a caching mechanism. This caching mechanism compiles the meta policy to PHP code which is applied to the node in subsequent requests. This improves the performance of the meta node and makes it also easier to debug your code. You can find the compiled code inside the meta subdirectory of the ATK temp directory. This directory has the following structure: meta/<module>/<node>.php. So this means that the compiled code for the node called "country" inside the module "example" can be found in <ATK temp>/meta/example/country.php.

During the development it might be better to turn off the caching mechanism because you might be still changing the structure of your database. To do so you can set the special $config_meta_caching configuration option to FALSE.

Conclusion

Well that's it for now. This howto should have given at least a small impression of the possibilities the atkMetaNode has to offer. Future versions will describe more (hidden) features of the atkMetaNode.

Personal tools
Navigation