Zend Framework

Using MySQL auto-increment values with Zend_Filter_File_Rename

The task is simple: Let the user (or someone else) upload an image to your server and store it in a predefined location. Additionally you would like to store some information, e.g. an user name, an image title and a comment in a MySQL-Db. As an experienced programmer you will maybe take the Zend Framework. To handle just a few images, this is no big thing. Make a Zend_Form and enable it to upload a file and store the information according to your model in the database. That is it.

Well thinking a little bit further you may think about the names of your files and what happens when there are two files (one on the server and one to be uploaded) with the same name. You have a conflict. To be more precise: without any intervention the newer file (the one to be uploaded) overwrites the existing file. You have to explain the user where the old image has gone and why there is another image twice in your database.

You have two solutions to this problem:

Scan your directory upon validation if there already exists a file with this name (or maybe you have stored the filename in the database and query the database) and decide what to do: rename file or do not permit upload, e.g. return a false on validation. You can use own filenames based on a MySQL auto-increment value, e.g. the ID-Column which holds the additional information (user name, title, comment). I will show you how to implement the latter solution. In a second tutorial I will show you how to access this files.

Setting up the application with Zend_Tool

To speed things up I have used Zend_Tool from command line to create my application. I have to say, that this is really nice. You will find some instructions on the Zend Devzone from Ralph Schindler.

Creating the models

After having a clean install from Zend_Tool you need to set up your model. Its a simple table as you can see in the following excerpt:

CREATE TABLE `images` (
  `id` tinyint(100) NOT NULL auto_increment,
  `title` varchar(200) NOT NULL,
  `comment` varchar(400) default NULL,
  `updated` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

You could also set the comment as text if you want. I do not want to long comments, so I decided to use Varchar.

Routines for the model

You need three to four routines for your model:

  • Read all entries (optional)
  • Read one entry
  • Write new entry
  • Get last id. My model looks like this:
<?php
//...
protected $_name = 'images';
	<br>
	public function getImages()<br>
	{<br>
		<br>
	}<br>
	<br>
	public function getImage($id)<br>
	{<br>
		$id = (int)$id;<br>
		$row = $this->fetchRow('id = ' . $id );<br>
		if(!$row) {<br>
			throw new Exception("Could not find row $id");<br>
		}<br>
		return $row->toArray();<br>
	}<br>
	<br>
	public function insertImage($title, $comment, $user)<br>
	{<br>
		$data = array(<br>
			'title' => $title,<br>
			'comment' => $comment,<br>
			'fbUser_id' => $user<br>
		);<br>
		<br>
		$this->insert($data);<br>
	}<br>
	<br>
	public function lastID()<br>
	{<br>
		return $this->_db->lastInsertId();<br>
	}<br>
//..
?>

The most important part is the function I called lastID().

Setting up a form

I do not have to explain here how you can set up a form. Just make a new form of enctype=“multipart/form-data” and use it in a controller you like.

Setting up the controller

As everybody knows does the upload “happen” in the controller in which the form is used. I have used an action called “Upload” as you can see in the code excerpt from the controller below:

<?php
//...
public function uploadAction()
    {
        $this->view->title = "Upload new Image";
		$this->view->headTitle($this->view->title, 'PREPEND');
		
		$form = new Form_ImageUpload();
		$form->submit->setLabel('Upload');
		if(!$this->getRequest()->isPost())
		{
			$this->view->form = $form;
		}
		elseif(!$form->isValid($_POST))
		{
			$this->view->failedValidation = true;
			$this->view->form = $form;
			return;
		}
		else
		{
			$values = $form->getValues();
			$uploadname = $form->image->getFileName();

			chmod('<path info="">/uploads/'.$uploadname, 755); // not necessary but worked fine for me
			$table = new Model_DbTable_Images;
			$table->insertImage($values['title'], $values['comment'], $this->user);
			$fullFilePath = '<path info="">/uploads/'.$table->lastID().self::findexts($uploadname);
			$filterFileRename = new Zend_Filter_File_Rename(array('target' => $fullFilePath, 'overwrite' => true));
			$filterFileRename->filter($uploadname);
			$this->view->imageUploaded = true;
			chmod($fullFilePath, 0755);
			$this->_redirect('index');
		}
		
	
    }
</path></path>
?>

As you can see are there some special functions involved:

  • You need your last id which has been inserted in the database respectively on the table. You can get this by invoking the function on the model, we have created above.
  • You need your filename and separately the file extension. But you need the extension only, if you allow multiple formats of images. If you only allow one format, then you do not need the extension, because you can add the default extension.
  • Specify the path, where the file has been saved to (you do not need to use “->setValueDisabled(true)” on the file upload element in your form) and where it will go to. In my case this paths are identical (because we just rename the file and do not copy it). Of course I will not provide my paths here. Please just use your own path instead of in the script (be careful to provide a full path from the root).
  • Put together all the information for the new path and invoke the Zend_Filter_File_Rename on the uploaded image.

Last trick: Getting a file extension

To get your file extension I have a separate function. Quite handy and nice, which you will also find multiple times, when you search in Google. I used this one here from Angela Bradley On the second page you will find the following code:

<?php
//This function separates the extension from the rest of the file name and returns it 
function findexts ($filename) 
{ 
$filename = strtolower($filename) ; 
$exts = split("[/\\.]", $filename) ; 
$n = count($exts)-1; 
$exts = $exts[$n]; 
return $exts; 
} 
?>

Declare it in your controller as private function and use it as shown above.

That is already all. You need a chmod() that your browser can read the image afterwards. Maybe you want like me a redirect or a success page then make another action or view to display to the user.

In the next tutorial I will show how to access the images and its information with a MySQL query and some directory-scanning.

Building a small gallery with Zend Framework and MySQL

Well now as we have stored our images within the filesystem of the web server and the information in the MySQL database (see above), it is about accessing the files and the information.

Basically the needed steps are quite easy:

  • Prepare SQL statements in your model for data
  • Create Controller and View Template
  • Create a View Helper Plugin to grab the image.

I will not explain in detail how to build the SQL statements respectively the functions in the model and how to set up the Controller and View Template. You will find other posts on this blog about this topic and with links to good tutorials So let us start: I assume you have already set up your Controller and View Template by now and you have also a query function in your model which fits your architecture. I have used a select combined with a fetchAll() to return my data.

Now in the View Template we have the usual loop, like the one shown below:

<br>
<?php if(count($this->images) > 0) : ?><br>
<?php foreach($this->images as $image) : ?><br>
// code here left out<br>
<?php endforeach; ?><br>
<?php endif; ?><br>

I did not use an else clause here, but for sure you can use this as well.

Now how to load the image within a HTML img-Tag?

According to the first post we have used the id value as file name (maybe you have renamed your id, then please use appropriate identifier for the scripts to follow). So we can access the id with:

<br>
$image->id<br>

We have 2 challenges here:

We need to have a path to the image We need to know the file-extension of the image (in case we allow different filetypes). Well one solution could be to store the path and the extension also in the database and return it as a property of the object $image (in my case). But this would need at least 2 queries for the saving of the information, because image is transferred after SQL query in my case (see previous post).

This is why I have chosen another method, which gives me nearly the same flexibility: I use a View Helper. So then go on an create a View Helper. I called mine findImageById and it takes the id as argument.

<br>
<?php<br />
<br>
class Zend_View_Helper_FindImageById<br>
{<br>
	function FindImageById($id)<br>
	{<br>
		$dir = '<path folder="" public="" to="">/public/uploads';<br>
<br>
		$images = scandir($dir);<br>
<br>
		foreach($images as $image)<br>
		{<br>
			$path_parts = pathinfo($dir.'/'.$image);<br>
			if( $path_parts['filename'] == $id )<br>
			{<br>
				$found = $image;<br>
			}<br>
		}<br>
		return $found;<br>
	}<br>
}<br>
<br>
?><br>
</path>

Quite a handy function, isn’t it? The only thing this helper does is to scan the directory provided under $dir and compare the filenames (without extension) with the provided argument, e.g. the id of our query. As soon as one has been found the filename will be returned.

Extending the View Template

Now the only thing is to extend the View Template accordingly, as shown below:

<br>
<ul style="list-style-type: none;"><br>
<?php if(count($this->images) > 0) : ?><br>
<?php foreach($this->images as $image) : ?><br>
<li><br>
	<img alt="<?php echo $image->title ?>" src="<!--http-path here--><?php echo $this->findImageById($image->id); ?>" <br="">
	width="200"/><br>
</li><br>
<?php endforeach; ?><br>
<?php endif; ?><br>
</ul><br>

Of course can you use any other format. I have just used an unordered list here for this simple demo case. But tables or floating divs would be possible too.

Dynamic Select-List in Zend_Form

Recently I decided to set up my new homepage with the Zend Framework, since it is one of the more mature frameworks and they have a consistent development of the framework, this means no bigger changes in coding style since version 1.2 or so. Other frameworks may be good as well and have maybe even more sophisticated methods for a rapid application development (RAD), but this is not to discuss here.

I started two weeks ago, mainly on the weekends to work on the tutorial from Pádraic Brady which he published on his blog “Maugrim The Reaper’s Blog”. The tutorial, which as announced will be revisited, can be found here.

After working through this tutorial you will be able to work on your own with the framework pretty well. One of the first problems I encountered was, that I had to use Selects in the forms to link with other components of my application respectively set a foreign key into the MySQL-Table.
Well, looking at the documentation you will find a lot of examples, but none has fit right into the coding style of the application, since the tutorial is using one of the fastest annotations possible in Zend Framework. The flexibility of the framework is maybe one aspect that may a beginner confuse a little bit.
But within a few searches I found help:
So enough material to work on. This is then what I made of it:

  1. Write a little private function in the custom Form which selects all the categories and their id’s:
private function _categorySelection()
{
	$table = new LinkCategories;
	// fetch all rows
	$row = $table->fetchAll(
		$table->select()->from($table, array('id', 'lcat_title'))
	);

// init array
$options = array();

foreach($row as $option)
{
	$options[$option->id] = $option->lcat_title;
}

return $options;
}

2. Call the function within the declarations of the options:

$this->addElement('select', 'category', array( 
'decorators' => $this->_standardElementDecorator, 
'label' => 'Category:',
'required' => true,
'multiOptions' => $this->_categorySelection()
 ));

This is all. The code is tested an runs under 1.7.0. It even works with the editing form, the right selections is marked when reopening respectively editing a previous saved entry.

Use Smarty’s Mail-Encoding in Zend Framework

As soon as you have a nice front end of your website, which should be functional (and compliant with current regulations), you will have to provide a contact form or a email-address somewhere. I have decided to show only my email-address, but wanted to protect it at least with a minimal encryption from mail-harvesters.

In an earlier version of my page I have used the template engine Smarty, which has some functions to encrypt your email-address with JavaScript. And I have to say that this worked fine for me so far. So my current page uses Zend Framework and I am not the great JavaScript-Coder. This is why I decided to try to merge this two codes together. And it works. Here comes how.

First you need to download Smarty. - You need a running Zend Framework Site. - And an editor.

After you have downloaded and extracted the Smarty Template Engine, you need to identify the corresponding function. It is called Mailto and is in the file function.mailto.php. The path is:

/Your Path To Smarty/Smarty/plugins/function.mailto.php

Insert function.mailto.php as View Helper

Open the file in your preferred editor. And you should see the code and the function declaration.

Now first a few preparations for the Zend Framework: Create a new View Helper and call it however you want. I will call mine EncodeMail, so the file created is EncodeMail.php under the View Helper Directory (see Zend Reference Guide).

Now go back to the Smarty function.mailto.php file in your editor. Mark all text and copy it (e.g. with Ctrl + C).

Open the EncodeMail View Helper and paste (e.g. with Ctrl + V) the previously marked Smarty function into this file. Save the View Helper.

Make minor adjustments to the View Helper

Now you need to make some minor adjustments/corrections in the new created View Helper.

The first thing is to make it a class. As you can see from the code it is “only” a function. So you need to declare it as a class.
class xxx_View_Helper_encodeMail
{
// Here is the smarty code
}
Next step is to redeclare the Smarty function to be compatible with the current environment from Zend Framework. The original function looks like this:
function smarty_function_mailto($params, &$smarty)
{
...
}
Change it to:
public function encodeMail($params)
	{
...
}
As you can see do I have omitted one parameter (actually one by reference) to the $smarty variable. This makes a few more adjustments necessary. You need to change this expression:
$smarty->trigger_error("Any String Message");
To this one:
trigger_error("A String Message");

You need to do this for all the lines where this code is shown. This is used by Smarty for the error-handling, which Zend Framework does different.

If you have done this, save your new View Helper and use it with:
 'me@mail.com', 'encode' => 'javascript', 'text' => 'Contact Me');
echo $this->encodeMail($contact);
?>

Quite simple, isn’t it?