Writing Components -- Defining Your Data

Published
2007-07-15 14:52
Written by
This is Part 2 of my series on Writing Components For CiviCRM. Part 1 discussed what a component is, and what it does. The key tasks were:
  • Define and manage a set of data base tables.
  • Set up UI to view and edit contacts that contain its data.
  • Make the content searchable.
I'm going to discuss mostly the first of these in this post: how you get CiviCRM to build and "see" your tables. The first part of this is fairly intuitive, and Lobo and the rest of the CiviCRM folks have made it fairly easy, by making most of the low level access to your data "automagic". Since some of the files I'm referring to are only in the Subversion repository (and don't get installed to your system in a tarball), you may want to check out CiviCRM using your SVN client. I'm using a heavily modified 1.6 here, although I suspect most of what I'm describing here will also be true for 1.7 and 1.8 as well. So how does all this "automagic" work? The key to this is using XML file definitions for your tables, and getting CiviCRM to build your tables and the low level PHP class files that take care of loading data for you and writing it back. One of the data types in the Voter component is a Campaign, which represents a single election or issues campaign. Its XML definition file (cleaned up a bit for clarity) looks like this:

<table>
  <base>CRM/Voter</base>
  <class>Campaign</class>
  <name>civicrm_voter_campaign</name>
  <comment>Campaign objects represent an election for a candidate or issue.</comment>
  <add>1.6</add>
  <field>
      <name>id</name>
      <type>int unsigned</type>
      <required>true</required>
      <comment>Unique Campaign ID</comment>
      <add>1.6</add>
  </field>
  <primaryKey>
       <name>id</name>
       <autoincrement>true</autoincrement>
  </primaryKey>
   <field>
      <name>default_campaign</name>
      <title>Default Campaign</title>
      <type>boolean</type>
      <default>0</default>
      <comment>Default campaign for this install.</comment>
      <add>1.6</add>  
  </field>
  <field>
      <name>campaign_name</name>
      <required>true</required>
      <title>Campaign Title</title>
     <type>varchar</type>
      <length>30</length>
      <comment>Display name or title of the campaign</comment>
      <add>1.6</add>  
  </field>
  <field>  
      <name>start_date</name>  
      <uniqueName>campaign_start_date</uniqueName>
      <title>Campaign Start Date</title>
      <type>date</type>
      <comment>Opening of political campaign.</comment>
      <add>1.6</add>  
 </field> 
<field>  
      <name>election_date</name>  
      <title>Date Of Election</title>
      <type>date</type>
      <comment>Opening of political campaign.</comment>
      <add>1.6</add>  
 </field> 
</table>
Let's look at some of these directives. <base> tells CiviCRM's build system (more on that below) where you want the builder to put any PHP files it generates for you. This is relative to the top-level CiviCRM directory, where you'll find the api/, CRM/, sql/, templates/ and xml/ directories. The <class> directive tells the builder how to define the PHP loader and wrapper classes. CiviCRM uses the PHP PEAR DB_DataObject classes to handle most of the dirty work of getting at your data. It does this via a CiviCRM base class CRM_Core_DAO, which is defined in CRM/Core/DAO.php. CRM_Core_DAO handles your INSERT, UPDATE and DELETE statements, and you'll very rarely need to worry about these yourself. Every table in CiviCRM has a corresponding DAO (i.e., "Data Access Object") class, named using your value of class, and the "path" you gave in base. For the Campaign object:
  • The builder will define a class CRM_Voter_DAO_Campaign, and
  • The builder will put this definition in a CRM/Voter/DAO/Campaign.php file.
Voter currently has 5 classes: Campaign, Canvass, Voter, VoterInfo, and Canvass_Contact, each with its own XML file to describe its structure, primary and foreign keys, and indexes. The files also define helper data used by the Export and Import subsystems in CiviCRM. Once you write your XML data definitions, you need to put them somewhere that the builder will find them. For Voter, I first create a directory xml/schema/Voter for my files and add to it my file list, another xml file called files.xml: <tables xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include href="Voter.xml" parse="xml" /> <xi:include href="VoterInfo.xml" parse="xml" /> <xi:include href="Campaign.xml" parse="xml" /> <xi:include href="Canvass.xml" parse="xml" /> <xi:include href="CanvassContact.xml" parse="xml" /> <xi:include href="Districts.xml" parse="xml" /> </tables> To get the builder to load this, the last step is to add the path to your files.xml file to the list in xml/schema/Schema.xml. It should be fairly obvious what to change. Once you've done this, you can run the builder utility, which is in distmaker/distmaker.sh. You define a distmaker.conf file telling the builder where you want distmaker to put your files, and run it from the shell to generate a tar ball. To build the PHP5 version for Drupal, you type: $ ./distmaker d5 If you have everything set up right, the script will generate a version civicrm_41.mysql that contains your newly defined tables, and a set of DAO class files to handle low level access. You're still not quite done yet, however. You'll need to create BAO ("Business-logic Access Object") class definition files for each of your tables. The easiest thing here is to use another component as a model. I used Membership and Quest (a project Lobo & Co. did for a group of university admissions departments) as mine. That's enough for today. I'll describe how to get your various files loaded, and how to export an API in the next installment.
Filed under