Reverse Engineer xPDO Classes from Existing Database Table
Last edited by Novriko Parhusip on Feb 11, 2015.
Introduction
The xPDO Object-Relational-Bridge (ORB) relies on a series of PHP classes to provide an interface to database tables. These PHP classes can be generated automatically by parsing a specially formatted XML file, by reverse engineering existing database tables, or they can even be written by hand (masochists only). The easiest approach when dealing with a custom database table is to reverse engineer existing MySQL database tables: MySQL has been around for a long time, and there are numerous tutorials and books out there to help you learn how to use it.
Our process will be this:
- Create a database table (or tables) using MySQL (this can be done via the mysql command line or any number of MySQL GUI clients, e.g. phpMyAdmin or SQL-Yog).
- Copy the "reverse-engineering" script (provided below) to your webserver. Put it at the root of your MODx install (this is important so the script can find xPDO). This script uses the xPDO classes to sniff out the definition of the table you just created.
- If needed, modify the generated XML definition file to define foreign key relationships, then re-run the script to regenerate the class files.
- Connect your newly created class and schema files to a Snippet or Custom Manager Page.
Even if you plan to deploy your code and its associated data models onto multiple other other platforms, it's generally considered much easier to develop it with a single database in mind. Once you've done that, you can then focus on abstraction later. You can of course jump right into the xPDO definitions and classes that will define database-agnostic classes and schemas, but it is more difficult for the novice precisely because it deals with abstractions. The further you get from concrete examples, the more difficult the development becomes.
Access Points
xPDO is the engine behind this database abstraction – ultimately it needs PHP classes that describe the data model. You can supply an XML schema which will generate the PHP files which will in turn generate the necessary tables – this is how third-party components are distributed because it provides a predictable and unified way of creating new database tables. But in this example, we're going to start with a database table and use that to generate the XML schema, which will in turn generate the necessary PHP classes.
In the image below, it's important to realize that you can start with any one component, and the other 2 can be automatically generated.
Arguably, the easiest "access point" to the xPDO technology is to start with some existing database tables and use those to generate the XML schema file and PHP classes, and that's what this page demonstrates.
Creating a MySQL table
One of the easiest ways to create a MySQL table is to use one of the many GUI editors available. SQL-Yog is a great desktop application for MySQL management on Windows, Macs offer Sequel Pro. If you are using a web application, phpMyAdmin is nearly ubiquitous.
Create Reverse Engineering Script
We need a script to scan your database tables and generate the XML schema and PHP files. In general, this is a "disposable" script that you may only need to run once. You will probably need to make adjustments and run it more than once, but in concept and in function, this script is merely scaffolding.
The crux of this script are 2 xPDO methods (note, however, that the methods belong to children objects):
- writeSchema
- parseSchema
Together, they behave similarly to other ORM's, e.g. Doctrine
// Sample Doctrine code: Doctrine_Core::generateModelsFromDb();
Here's a reverse-engineering script that allows a bit of configuration and does a little error checking:
Reverse Engineering ErrorMODX_CORE_PATH not defined! Did you include the correct config file?
'); exit; } $xpdo_path = strtr(MODX_CORE_PATH . 'xpdo/xpdo.class.php', '\\', '/'); include_once ( $xpdo_path ); // A few definitions of files/folders: $package_dir = MODX_CORE_PATH . "components/$package_name/"; $model_dir = MODX_CORE_PATH . "components/$package_name/model/"; $class_dir = MODX_CORE_PATH . "components/$package_name/model/$package_name"; $schema_dir = MODX_CORE_PATH . "components/$package_name/model/schema"; $mysql_class_dir = MODX_CORE_PATH . "components/$package_name/model/$package_name/mysql"; $xml_schema_file = MODX_CORE_PATH . "components/$package_name/model/schema/$package_name.mysql.schema.xml"; // A few variables used to track execution times. $mtime = microtime(); $mtime = explode(' ', $mtime); $mtime = $mtime[1] + $mtime[0]; $tstart = $mtime; // Validations if (empty($package_name)) { print_msg('Reverse Engineering Error
The $package_name cannot be empty! Please adjust the configuration and try again.
'); exit; } // Create directories if necessary $dirs = array($package_dir, $schema_dir, $mysql_class_dir, $class_dir); foreach ($dirs as $d) { if (!file_exists($d)) { if (!mkdir($d, 0777, true)) { print_msg(sprintf('Reverse Engineering Error
Error creating
%s
Create the directory (and its parents) and try again.
' , $d )); exit; } } if (!is_writable($d)) { print_msg(sprintf('Reverse Engineering Error
The
%s
directory is not writable by PHP.Adjust the permissions and try again.
' , $d)); exit; } } if ($verbose) { print_msg(sprintf('
Ok: The necessary directories exist and have the correct permissions inside of
%s
', $package_dir)); } // Delete/regenerate map files? if (file_exists($xml_schema_file) && !$regenerate_schema && $verbose) { print_msg(sprintf('
Ok: Using existing XML schema file:%s
', $xml_schema_file)); } $xpdo = new xPDO("mysql:host=$database_server;dbname=$dbase", $database_user, $database_password, $table_prefix); // Set the package name and root path of that package $xpdo->setPackage($package_name, $package_dir, $package_dir); $xpdo->setDebug($debug); $manager = $xpdo->getManager(); $generator = $manager->getGenerator(); $time = time(); //Use this to create an XML schema from an existing database if ($regenerate_schema) { if (is_file($xml_schema_file)) { $rename = $xml_schema_file . '-' . $time; print_msg("
The old XML schema file:{$xml_schema_file}
has been renamed to{$rename}
."); rename($xml_schema_file, $rename); } $xml = $generator->writeSchema($xml_schema_file, $package_name, 'xPDOObject', $table_prefix, $restrict_prefix); if ($verbose) { print_msg(sprintf('
Ok: XML schema file generated:%s
', $xml_schema_file)); } } // Use this to generate classes and maps from your schema if ($regenerate_classes) { if (is_dir($class_dir)) { $rename = $class_dir . '-' . $time; print_msg("
The old class dir:{$class_dir}
has been renamed to{$rename}
."); rename($class_dir, $rename); } $generator->parseSchema($xml_schema_file, $model_dir); } $mtime = microtime(); $mtime = explode(" ", $mtime); $mtime = $mtime[1] + $mtime[0]; $tend = $mtime; $totalTime = ($tend - $tstart); $totalTime = sprintf("%2.4f s", $totalTime); if ($verbose) { print_msg("
Finished! Execution time: {$totalTime}
"); if ($regenerate_schema) { print_msg("
If you need to define aggregate/composite relationships in your XML schema file, be sure to regenerate your class files."); } } exit(); /* ------------------------------------------------------------------------------ Formats/prints messages. The behavior is different if the script is run via the command line (cli). ------------------------------------------------------------------------------ */ function print_msg($msg) { if (php_sapi_name() == 'cli') { $msg = preg_replace('#
#i', "\n", $msg); $msg = preg_replace('##i', '== ', $msg); $msg = preg_replace('#
#i', ' ==', $msg); $msg = preg_replace('##i', '=== ', $msg); $msg = preg_replace('#
#i', ' ===', $msg); $msg = strip_tags($msg) . "\n"; } print $msg; } /* EOF */
To check whether or not this script succeeded, take a look inside the folder that is mentioned in its output, e.g.
/user/youruser/public_html/core/components/yourpackage/model/yourpackage. You should see a couple files – one for each table. If you see a TON of tables corresponding to all of MODx's tables, then try to explicitly set the database password and name – leave the following line commented out:
//include('core/config/config.inc.php');
See http://modxcms.com/forums/index.php?topic=40174.0 for more discussion on this script.
Defining Key Relationships
Once you have your XML schema file generated, you may need to edit it manually to define any foreign key relationships between your tables. It's best if you create a backup of the XML schema file, then add in your aggregate and composite relationships (see Schema Files and Relations for more info).
In the scaffolding script above, set the following:
$regenerate_schema = false;
Then re-run the script in order to push your changes in the XML to the PHP class files.
Accessing your Data
Once you've created the required xPDO classes, you need to use xPDO's methods to access them (e.g. in a Snippet or in a Custom Manager Page). In order for xPDO to access the objects, you have to load up the corresponding PHP classes using the addPackage method. addPackage is what triggers the PHP classes to be included.
if(!$modx->addPackage('mypackage','/full/path/to/core/components/mypackage/model/','mp_')) { return 'There was a problem adding your package! Check the logs for more info!'; } $my_items = $modx->getCollection('Items'); $output = ''; if ($my_items) { foreach ($my_items as $item) { $output .= $item->get('itemname') . '<br/>'; } } else { return 'No items found.'; } return $output;
addPackage requires that you specify the correct table prefix for your package!
See Also
- Schema Files and Relations Looking at XML schema file relations
- addPackage for loading up your schema
- getObject for loading up a single object
- getCollection for loading up a collection of objects.
- xPDO: Creating Objects
- Retrieving Objects a demonstration of how to retrieve objects using xPDO
- Generating the Model Code – offers a streamlined version of the script provided here, but you can also change your class templates.
- More Examples of xPDO XML Schema Files – juxtaposes MySQL database tables with xPDO XML schemas
- Build script: Reverse-engineering tables / forward-engineering classes / maps – another example by Jason.
Suggest an edit to this page on GitHub (Requires GitHub account. Opens a new window/tab).