Description:This tutorial shows how to take a basic mixed PHP/HTML file and refactor it for MODx Revolution.
This is for folks who are somewhat comfortable working with PHP but are still learning the basics of MODx. If you fit most of the following description, this tuturial should be helpful:
The example I'll be working from is a simplified version of a real-world project I recently completed. The application was developed in straight PHP/HTML and did the following:
You can see the actual PHP application on the web here, if you are interested.
The original source code for this application looked something like this (simplified for clarity):
File: acctCodes.php (original version)
<html>
<head>
<title>SFS Account Codes and Definitions</title>
</head>
<body>
<h1>SFS Account Codes and Definitions</title>
<form>
<!-- the first SELECT box -->
<div>
<label for="byCategory">Find by Category:</label>
<select name="byCategory" id="byCategory">
<option>Pick a Category</option>
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
foreach ($values as $value) {
echo '<option value="' . $value . '">' . $value . '</option>';
}
?>
</select>
</div>
<!-- the second SELECT box -->
<div>
<label for="byType">Find by Type:</label>
<select name="byType" id="byType">
<option>Pick a Type</option>
<?php
$values = // some code that runs a query to get the values for the 2nd dropdown
foreach ($values as $value) {
echo '<option value="' . $value . '">' . $value . '</option>';
}
?>
</select>
</div>
<!-- the account codes table -->
<table id="acctCodes">
<thead>
<!-- you know what <th> cells look like. I'm simplifying here -->
</thead>
<tbody>
<?php
$records = // some code that runs a query to get the records for the table
foreach ($records as $record) {
?>
<tr>
<td><?php echo $record['category']; ?></td>
<td><?php echo $record['status']; ?></td>
<td><?php echo $record['account']; ?></td>
<td><?php echo $record['acctType']; ?></td>
<td><?php echo $record['title']; ?></td>
<td><?php echo $record['definition']; ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
</body>
</html>
The PHP here is pretty simple; there are three separate blocks of code where a query is run and then the results are echoed to the page, wrapped in some appropriate HTML tags. The third block gets a little fancy, escaping out of PHP while inside the loop to allow some straight HTML code rather than a big obnoxious "echo" statement. But nothing too terribly complicated is going on here.
Still, we are going to need to do some cleanup to get this ready to go in to MODx.
The first step to cleaning up this PHP code for MODx (and in general) is to get away from mixing application logic with the page markup. Instead of running each query right before we use it, we'll run all of the queries first, then just echo the output where needed.
This ends up looking something like this (again, very simplified for clarity):
File: acctCodes.php (cleaned-up version)
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
$options1 = '';
foreach ($values as $value) {
$options1 .='<option value="' . $value . '">' . $value . '</option>';
}
$values = // some code that runs a query to get the values for the 2nd dropdown
$options2 = '';
foreach ($values as $value) {
$options2 .= '<option value="' . $value . '">' . $value . '</option>';
}
$records = // some code that runs a query to get the records for the table
$trrows = '';
foreach ($records as $record) {
$trrows .= '<tr><td>' . $record['category'] .
'</td><td> . $record['status'] .
'</td> <td>' . $record['account'] .
'</td> <td>' . $record['acctType'] .
'</td> <td> . $record['title'] .
'</td> <td> . $record['definition'] .
'</td></tr>';
}
?>
<html>
<head>
<title>SFS Account Codes and Definitions</title>
</head>
<body>
<h1>SFS Account Codes and Definitions</title>
<form>
<!-- the first SELECT box -->
<div>
<label for="byCategory">Find by Category:</label>
<select name="byCategory" id="byCategory">
<option>Pick a Category</option>
<?php echo $options1; ?>
</select>
</div>
<!-- the second SELECT box -->
<div>
<label for="byType">Find by Type:</label>
<select name="byType" id="byType">
<option>Pick a Type</option>
<?php echo $options2; ?>
</select>
</div>
<!-- the account codes table -->
<table id="acctCodes">
<thead>
<!-- you know what <th> cells look like. I'm simplifying here -->
</thead>
<tbody>
<?php echo $trrows; ?>
</tbody>
</table>
</body>
</html>
This makes the markup is much easier to look at and edit. All of the PHP is contained in four blocks of code: The first one does the queries and sets the values, and the next three simply echo the values to the screen.
This puts us in much better shape to bring this little web application into MODx.
To bring our cleaned up code into MODx, we'll need to make a few additional changes. Our HTML can go straight in to a MODx Resource. However, the PHP code cannot. We are going to need to replace it with a Snippet and some Placeholders.
Our MODx resource content is going to look like this:
MODx Resource: 'acctCodes'
[[accountCodes]]
<html>
<head>
<title>SFS Account Codes and Definitions</title>
</head>
<body>
<h1>SFS Account Codes and Definitions</title>
<form>
<!-- the first SELECT box -->
<div>
<label for="byCategory">Find by Category:</label>
<select name="byCategory" id="byCategory">
<option>Pick a Category</option>
[[+options1]]
</select>
</div>
<!-- the second SELECT box -->
<div>
<label for="byType">Find by Type:</label>
<select name="byType" id="byType">
<option>Pick a Type</option>
[[+options2]]
</select>
</div>
<!-- the account codes table -->
<table id="acctCodes">
<thead>
<!-- you know what <th> cells look like. I'm simplifying here -->
</thead>
<tbody>
[[+trrows]]
</tbody>
</table>
</body>
</html>
In our resource, the four blocks of PHP code have been replaced with MODx tags. The first one, [[accountCodes]] will run a Snippet called accountCodes (which we haven't written yet). The next three tags are Placeholders, which will be replaced by values that our Snippet will need to set.
Now we need to go and write that Snippet
Our "accountCodes" snippet is going to contain the code from that first PHP block - all of the query code and the loops that set build the HTML that gets output to the screen. The only thing we are going to add is a few lines of MODx API code that will set thouse output values as placeholder values (for our the placeholder tags in our resource).
snippet: "accountCodes"
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
$options1 = '';
foreach ($values as $value) {
$options1 .='<option value="' . $value . '">' . $value . '</option>';
}
$values = // some code that runs a query to get the values for the 2nd dropdown
$options2 = '';
foreach ($values as $value) {
$options2 .= '<option value="' . $value . '">' . $value . '</option>';
}
$records = // some code that runs a query to get the records for the table
$trrows = '';
foreach ($records as $record) {
$trrows .= '<tr><td>' . $record['category'] .
'</td><td> . $record['status'] .
'</td> <td>' . $record['account'] .
'</td> <td>' . $record['acctType'] .
'</td> <td> . $record['title'] .
'</td> <td> . $record['definition'] .
'</td></tr>';
}
$modx->setPlaceholder('options1', $options1);
$modx->setPlaceholder('options2', $options2);
$modx->setPlaceholder('trrows', $trrows);
?>
That's our snippet. The only additions from our original PHP code are the three "setPlaceholder" lines at the bottom. This simply tells MODx that if & when it should come across a placeholder [[+options1]] later on in the page, it should replace it with the value of $options1. Placeholders are MODx's simple and elegant way of handling output values from snippets.
We now have done everything we need to get our web application up and running inside of MODx. However, there are a few additional steps we can take to make our application much sexier, by utilizing more of MODx's API functionality. I'd rate our current implementation as a "C". Let's keep working on it.
One criticism that could be made of our current PHP code is that it is still mixing markup with logic. Our 'foreach' loops still contain hard-coded bits of HTML markup:
foreach ($values as $value) {
$options1 .= '<option value="' . $value . '">' . $value . '</option>';
}
This is a less-than-ideal strategy: suppose the boss asks Skippy the HTML code monkey to change the HTML for this application, adding some additional attribute to each <option> tag. We don't want Skippy the code monkey coming anywhere near our PHP script, right?
There are several ways to remedy this situation in PHP - pulling in the HTML from external files and doing string substitutions comes to mind. However, we don't need to worry about figuring out a solution like that. For this problem, MODx offers another simple and elegant solution: Chunks with Placeholders.
The "option" HTML from the above Snippet could be rewritten as a MODx chunk called "option":
chunk: "option"
<option value="[[+value]]">[[+value]]</option>
Nothing special about this chunk. All it is is a bit of HTML with a couple of placeholders in it. "option" is just a generic, utility chunk that can be used anywhere - by multiple snippets in multiple resources.
Now we can modify our snippet with a little more MODx API code to use the value of this chunk in our loop:
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
$options1 = '';
foreach ($values as $value) {
$modx->setPlaceholder('value', $value);
$options1 .= $modx->getChunk('option');
}
Now our loop sets a placeholder for $value, and concatenates the value of the 'option' chunk to the output string.
This allows us to completely separate all HTML code from our PHP script. If Skippy the HTML code monkey needs to add an attribute to the <option> elements, he can do it by editing the "option" chunk. (Just tell him not to touch those placeholders!)
The table rows can be done similarly:
chunk: "trrows"
<tr>
<td>[[+category]]</td>
<td>[[+status]]</td>
<td>[[+account]]</td>
<td>[[+acctType]]</td>
<td>[[+title]]</td>
<td>[[+definition]]</td>
</tr>
and the PHP
foreach ($records as $record) {
$trrows .= $modx->getChunk('trrows', $record);
}
Note that in this example, we don't even have to set any placeholders! Since $record is an associative array, we can pass it to getChunk() as an argument, and placeholders will automagically** be set for every key/value pair in the array!
(**MODX Awesomeness! You're soaking in it!**)
So now we can keep all of our HTML bits in chunks, and rewrite our Snippet like so:
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
$options1 = '';
foreach ($values as $value) {
$modx->setPlaceholder('value', $value);
$options1 .= $modx->getChunk('option');
}
$values = // some code that runs a query to get the values for the 2nd dropdown
$options2 = '';
foreach ($values as $value) {
$modx->setPlaceholder('value', $value);
$options2 .= $modx->getChunk('option');
}
$records = // some code that runs a query to get the records for the table
$trrows = '';
foreach ($records as $record) {
$trrows .= $modx->getChunk('trrows', $record);
}
$modx->setPlaceholder('options1', $options1);
$modx->setPlaceholder('options2', $options2);
$modx->setPlaceholder('trrows', $trrows);
?>
This is starting to look really nice. We are taking advantage of the MODx API to template our result rows, and our snippet is looking really clean. However, one thing about it bothers me and might be potentially problematic - we now have MODx API calls intermingled with our main application logic. If we are 100% positive that we are only ever going to need this data printed onto this web page and in this format, then this really isn't an issue. But if there is any possibility that this data might need to be used in another way - somewhere outside of MODx - it would be best to keep the main application logic separate from the MODx stuff. Right now our application design gets a "B". Lets take that final step and get our grade up to an "A".
All we have to do for the final step is split our snippet up in to two sections. The first section will contain our main application logic, and will return all of the data arrays as members of one larger array. This will be done with straight PHP, and ideally should be stored as a file on our server.
The second section will be the snippet itself. It will first get the array returned by the logic file, then use chunks to template the result rows, and finally set placeholders to display the results in the resource.
Here's how that all ends up looking:
File: acctCodes.php
<?php
//some code that connects to a database
$values = // some code that runs a query to get the values for the 1st dropdown
$values2 = // some code that runs a query to get the values for the 2nd dropdown
$records = // some code that runs a query to get the records for the table
return array('values'=>$values,
'values2'=>$values2,
'records'=>$records);
?>
Snippet: 'acctCodes'
<?php
$data = include_once(MY_INCLUDE_PATH . 'acctCodes.php');
$options1 = '';
foreach ($data['values'] as $value) {
$modx->setPlaceholder('value', $value);
$options1 .= $modx->getChunk('option');
}
$options2 = '';
foreach ($data['values2] as $value) {
$modx->setPlaceholder('value', $value);
$options2 .= $modx->getChunk('option');
}
$trrows = '';
foreach ($data['records'] as $record) {
$trrows .= $modx->getChunk('trrows', $record);
}
$modx->setPlaceholder('options1', $options1);
$modx->setPlaceholder('options2', $options2);
$modx->setPlaceholder('trrows', $trrows);
?>
Now we have our ducks in a row. Lets review what we've done:
We've come a long way from our original mix-and-match PHP/HTML file, haven't we? I'd grade this final implementation as an "A". That said, others will probably have some even better ideas at how to seperate the various areas of concern. But for PHP hacks like me, this is a good place to start.
Hopefully you've learned some of the key areas of concern when writing basic PHP applications for use in MODx. Feel free to leave any thoughts, questions or suggestions about this tutorial in the comments on the associated blog post.
.