============================================== Developing an Image Gallery with eZ components ============================================== :Author: Tobias Schlitt :Date: Wednesday 19 April 2006 10:35:00 am .. contents:: Table of Contents This tutorial shows you how to program a web application that stores photographs in an image gallery. It's based on libraries included with the eZ components, and so gives you a practical example of the library usage. The sample code of this article is available for download. This tutorial uses the eZ components to create a simple image gallery for a website. You can use the image gallery to upload photos and to organize them into albums. The gallery scales the photos to a given size, and also produces a thumbnail for previewing the images on an overview page. The image gallery application that will result from doing this tutorial is not completely ready for use in a production environment, but with a little more work, and using the concepts learned from this tutorial, it could be offered to the world (and your Auntie Erna). Setting up the environment ========================== Preparing the database ---------------------- Before we can start, we need to set up a database that will store the structure of our albums and the data of the photos. For development I used MySQL, but you can use any database supported by the eZ components database abstraction layer. Just create the following two tables and a user to access the database:: CREATE TABLE album ( id int(11) unsigned NOT NULL auto_increment, title varchar(200) NOT NULL default '', description text NOT NULL, PRIMARY KEY (id) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; This table will be used to store our album structure. As you can see, it is very simple. Each album has a title, a description and a numeric ID:: CREATE TABLE photo ( id int(11) unsigned NOT NULL auto_increment, album int(11) NOT NULL default '0', title varchar(200) NOT NULL default '', description text NOT NULL, PRIMARY KEY (id) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; The table to store data about our photos is as simple as the album table. It contains the numeric ID and the ID of the album where the photo belongs. It also contains a title and a description to tell Auntie Erna something about the pictures she's looking at. Creating Directories -------------------- After we create the database, we need to create the basic directory structure for our gallery: =========== =================================================================== Directory Description =========== =================================================================== actions/ The actions/ directory will contain controllers for the actions we want to implement. (As a good OO programmer, we use the Model View Controller (MVC) pattern.) ----------- ------------------------------------------------------------------- data/ data/ will contain the photos. ----------- ------------------------------------------------------------------- interfaces/ In interfaces/ we'll store some basic interfaces, for example our action controllers. ----------- ------------------------------------------------------------------- models/ models/ will contain the model classes for an album and a photo. ----------- ------------------------------------------------------------------- pos/ The pos/ directory is somewhat special and will contain definitions for the persistent objects. A persistent object is basically the implementation of the active record pattern in the eZ components. ----------- ------------------------------------------------------------------- templates/ The templates/ directory will store the HTML parts (the view of our MVC). Installing eZ components =========== =================================================================== Now we need to install the eZ components. (Hey, this is a tutorial about them, remember?!) The most convenient way to get them is to use the PEAR Installer. If you have PEAR installed, simply type:: $ pear channel-discover components.ez.no $ pear install ezc/eZComponents If your PEAR installation is properly configured, you're done! If you experience problems, check that your include_path is set correctly. Maybe you prefer to not use PEAR. Instead, you can download the eZ components tarball from our website. Crack it into a directory of your choice and make sure your include_path contains the directory you choose for extraction. The eZ UserInput component relies on the PECL extension ext/filter, so you have to install that too. The easiest way to do this is using the PEAR installer again:: $ pecl install filter-beta If you don't use PEAR, you can download the source package from the PECL website and compile the extension by hand. Note, that you have to install the pre-compiled binary distribution of the extension by hand, if you are running PHP on Windows. After these steps, you have to add the extension to your php.ini. MVC in the main script ====================== Finally we can get started with coding! The first piece of code goes into the file gallery.php, which is located in our application root directory. This is the main file that we will call from the web browser. It contains a lot of code, namely the autoload mechanism, the main controller class and the code to run it. But let's start slowly... Accessing the database and eZ components ---------------------------------------- :: First we require the eZ components base file. This is the only file we ever need to explicitly require. It contains the autoload mechanism, which we are activating by defining the __autoload() function right after the require statement. This is basically all we need to do to access all the classes contained in the eZ components library. Next we require a configuration file. (You need to change the values shown below to suit your environment.) Basically, the config file contains:: The DSN is a string that describes the connection to your database. The first part specifies the database engine to use. After that, the user and password are specified (in a similar manner to describing a URI). This account is used to connect to your database host (usually "localhost"). After the last slash ("/"), specify the database you created earlier. The second defined constant indicates where the files to include are located. You can change this if needed, but the example should usually work. The main controller class ------------------------- :: This is the basic structure of our main controller. Beside the constructor, it contains a run() method (that will dispatch to our action controllers) and a display() method (that will display the view after the action was executed). Additionally, we define two static methods as a singleton pattern for our database session ( getSession()) and the image converter (which I'll describe later). This will allow us to gain access to those instances by simply calling ezcGallery::getSession() anywhere in our application. Initialization -------------- So let's start with the constructor:: 'actions/listing.php', 'show' => 'actions/show.php', 'create' => 'actions/create.php', 'add' => 'actions/add.php', ); private $currentAction; public function __construct() { $action = 'listing'; if ( ezcInputForm::hasGetData() ) { $definition = array( 'action' => new ezcInputFormDefinitionElement( ezcInputFormDefinitionElement::REQUIRED, 'string' ), ); $form = new ezcInputForm( INPUT_GET, $definition ); $action = ( $form->hasValidData( 'action' ) ) ? $form->action : 'listing'; } if ( !isset( $this->actions[$action] ) ) { throw new Exception( "Invalid action <".htmlentities($action).">." ); } require_once $this->actions[$action]; $this->currentAction = new $action; } ?> First we define the actions we want our controller to dispatch. Defining those hard-coded in the controller is the most secure way to avoid potential security issues when using user-provided data to require files. Each action is assigned to the file we have to include to load the specific action. The second private property we need in the constructor is $currentAction, which will contain the action controller object that will handle the action. At first we initialize the variable $action to listing, which should be the action shown when no action is submitted by the user. After that we check for GET data in general, using the eZ UserInput component. If GET data is available, we define a rule set for UserInput to validate the data we expect - a variable action, which contains a string and must be set. Now we create an instance of ezcInputForm, which handles our GET data. Finally (at the end of the if statement) we assign the received action, if it was valid, or set the default action again. The next step is to check if the action we received is valid. If it is not, we throw an exception because we need a valid action to proceed. If the action was valid, we require the assigned file and instantiate the action controller. That's it for the initialization. Execution --------- Let's take a look at the run method:: currentAction->run(); } ?> This one is really short. It simply dispatches the run to the action controller. Display ------- So now let's go on to displaying the HTML. Because the eZ Template component is not ready yet, we are using HTML with embedded PHP for the View part of the MVC:: currentAction->getTemplateVars(); ob_start(); $res = include 'templates/' . $this->currentAction->getTemplate(); $html = ob_get_contents(); ob_end_clean(); if ( $res === false ) { throw new Exception( 'Template error.' ); } echo $html; } ?> As the first line of the display() method shows, every action controller implements a getTemplateVars() method to retrieve the objects needed to view the desired action. To avoid nasty errors displayed on our web site, we use PHP's output buffering to get the content of the filled template file. Every action controller knows which template to use since one action can have different templates (for example, one for a form and one for showing the results after submitting the form). Finally we display the HTML (if no error occurred). The getSession() and getConverter() methods are not of special interest right now. We will deal with them when we need them. Script execution ---------------- Finally, in gallery.php, we need to run our main controller, which is done like this (as you have probably already guessed):: run(); $gallery->display(); } catch ( Exception $e ) { die( $e->getMessage() ); } ?> With this, we have the base structure of our application ready. Now let's move on and try out some of the other eZ components beside the short code piece for UserInput which we already saw. Listing photo albums ==================== The first action we want to look at is for listing albums. If you want to run the application, you should now add a few test records to your database. The listing action resides in actions/listing.php. It requires two files from our application. Since we did not hook into the eZ components autoload mechanism, we need to require those explicitly:: The album model --------------- Our album model ( models/album.php) is fairly simple:: $this->id, 'title' => $this->title, 'description' => $this->description, ); } public function setState( array $properties ) { foreach ( $properties as $key => $val ) { $this->$key = $val; } } } ?> It contains three public properties which represent the fields of our database table. Since we want to use objects of this class as a persistent object, we need the two methods setState() and getState(), which will allow the eZ PersistentObject component to serialize the model to the database and fetch it back from there. Both methods deal with arrays that have the property names of the class as the keys, assigned to the values. Quite easy, isn't it? :) A persistent session -------------------- Before we take a look at the actual code of the action controller for listing albums, we should look at the ezcGallery::getSession() method, which we skipped earlier:: The method actually does not return a database connection, but a "persistent session." The difference is quite easy to explain: a database connection can be used to perform hand-written SQL actions on a database. A persistent session is more mighty and allows us to get our SQL generated and simply store, restore, update, delete, etc, our model objects without writing the SQL by hand. We introduce a new private, static member variable for our getSession() method: $persistentSession, which will keep the single instance of the session. If this variable is not yet set, we create it. Else, we simply return the instance it contains. To create a persistent session, we first create a new database connection using ezcDbFactory and the DSN we defined earlier in the main controller file. The eZ Database component already has a singleton mechanism in place which is initialized by ezcDbInstance::set(). Using this freshly created database connection, we go on and create a persistent session. The second parameter to ezcPersistentSession is a code manager, which deals with our persistent definitions. Remember that we set up a directory for those earlier? Give the name of this directory to the code manager. This is basically all we need to do to create the persistent session. Defining persistent objects --------------------------- The last thing we need to store our model in the database is a definition for the ezcPersistentCodeManager. This resides in the pos/ directory and has to be named according to the model class we want to store (album.php):: table = "album"; $def->class = "Album"; $def->idProperty = new ezcPersistentObjectIdProperty(); $def->idProperty->columnName = 'id'; $def->idProperty->propertyName = 'id'; $def->idProperty->generator = new ezcPersistentGeneratorDefinition( 'ezcPersistentSequenceGenerator' ); $def->properties['title'] = new ezcPersistentObjectProperty(); $def->properties['title']->columnName = 'title'; $def->properties['title']->propertyName = 'title'; $def->properties['title']->propertyType = 'string'; $def->properties['description'] = new ezcPersistentObjectProperty(); $def->properties['description']->columnName = 'description'; $def->properties['description']->propertyName = 'description'; $def->properties['description']->propertyType = 'string'; return $def; ?> Our definition is an instance of ezcPersistentObjectDefinition. It has basically three attributes that define the database table to use for the objects, the class for the model, and the property of the persistent object to use as the ID. While the first two are simple strings, the third consists of a struct class, named ezcPersistentObjectIdProperty. The column name and property name again refer to the table and the class. The generator defines the way in which the IDs for the object are generated. Basically, when we store a new object, we want to generate a new ID, so we use the ezcPersistentSequenceGenerator. This one relies on the database-specific sequence mechanism, so in my case on MySQL's auto_increment. Finally, we define the mappings for the rest of our model class attributes to table columns, which is quite self-explanatory, and return the definition. (I did not know this before, but you can return something from a file in PHP, which will then be returned from the require or include statement when calling it. Nice feature!) Handling persistent objects --------------------------- Now we have all preconditions in place to start dealing with the persistent objects. Remember, we did the following so far: * Wrote the model class * Instantiated a persistent session * Defined the mapping between the model class and the database table Now let's see how the action controller works with the object:: albums = ezcGallery::getSession()->find( ezcGallery::getSession()->createFindQuery( 'Album' ), 'Album' ); } public function getTemplate() { return 'listing.php'; } public function getTemplateVars() { return array( 'albums' => $this->albums ); } } ?> The constructor of the class does nothing. It just has to be in place, because I defined it inside the action interface. The run method fetches all of our albums and stores them in the private member variable $albums. This is a very interesting part: for fetching our persistent objects, we call the method find() on our persistent session. The find method returns an array of objects it found for the find query we submitted. We have to specify the model class we are looking for in the database (second parameter to find()). The find query we use is again built on the basis of the persistent session. In this case, we only use a very simple query which basically generates a SELECT * FROM Album for us, since we want to grab all albums. Later in this tutorial, we will see more on this. Hey, we're almost done! That was easy, wasn't it? The getTemplate() method just returns a hard-coded template name and getTemplateVars() returns the albums to display. So let's go on to the view associated with this action. Viewing the listing ------------------- ::
[Create album] |