Building My First Application with Zend, Part 3


One thing the example from the tutorial was lacking was pushing out return messages. The code kind of automatically tells you if there was an error, but the failed validation return was kind of ugly. We also like to let our users know something worked as well, otherwise they think it didn't and just keep doing it. Zend uses a function similar to what CakePHP had, a "flash message" system, so it didn't take me long to find the general things I'd need for pushing out messages, but the exact way it worked took a little longer to figure out.

The documentation mentions the addSuccessMessage and addErrorMessage type functions, for adding specific types of flash messages, but it doesn't show you how to retrieve those. For me, the render wasn't working particularly well. I then stumbled on their mentioning of namespaces and realized that it was how they "grouped" messages. Since our form returns might have multiple error messages, this seemed like a better way to deal with it anyway.

For our success messages, in my FormatsController.php, I just added one line just before the redirect back to index when something went right.

$this->flashMessenger()->setNamespace("success")->addMessage('Format added successfully!');

The edited success message was, of course, modified appropriately. 😉

Changing the error messages, however, took more work (and a few more hours of searching, cursing, etc). (As an aside – seriously, Google, WTF is with your algorithms these days? You put CF 8 results above 9 and 10, and Zend 1 results above Zend 2, and crap from 2006 above stuff that's from the current decade????)

Adding these required changing several files, particularly as our designs generally have error messages grouped together at the top of the page rather than on a per element basis as we see with Zend's default settings. So first, in the FormatsController.php, I added an else statement to my IsValid if statement.

if ($form->isValid()) {
	$formats->exchangeArray($form->getData());
	$this->getFormatsTable()->saveFormats($formats);
	$this->flashMessenger()->setNamespace("success")->addMessage('Format added successfully!'); //HERE IS THAT ADD MESSAGE 🙂
	
	// Redirect to list of formatss
	return $this->redirect()->toRoute('formats');
}
else { // ADDED THIS ELSE!
	$formErrors = $form->getMessages();
	foreach ($formErrors as $thisError) {
		$this->flashMessenger()->setNamespace("error")->addMessage($thisError);
	}
}

This else loops through the form error messages array and puts it into the flashMessenger with the error namespace. As far as I could tell, there was no way to just copy over the array or pass it in all at once, so looping we do. 🙂

Then in Formats.php, I had to add another use statement to the top and modify my filters to put in the messages.

<?php
	namespace MovieShelves\Formats\Model;
	
	use Zend\InputFilter\InputFilter;
	use Zend\InputFilter\InputFilterAwareInterface;
	use Zend\InputFilter\InputFilterInterface;
	use Zend\Validator\NotEmpty; // ADDED!
	
	class Formats {
		...[SNIPPED]...
		
		public function getInputFilter() {
			if (!$this->inputFilter) {
				$inputFilter = new InputFilter();
				
				$inputFilter->add(array(
					'name'     => 'formatid',
					'required' => true,
					'filters'  => array(
						array('name' => 'Int'),
					),
				));
				
				$inputFilter->add(array(
					'name'     => 'formatlabel',
					'required' => true,
					'filters'  => array(
						array('name' => 'StripTags'),
						array('name' => 'StringTrim'),
					),
					'validators' => array(
						array(
							'name'	=> 'NotEmpty',
							'options'		=> array(
								'messages' => array( //ADD FOR REQUIRED
									NotEmpty::IS_EMPTY => 'Format label is required',
								)
							),
							'break_chain_on_failure' => true
						),
						array(
							'name'    => 'StringLength',
							'options' => array(
								'encoding' => 'UTF-8',
								'max'      => 100,
								'messages' => array( // ADDED FOR MAX LENGTH
									'stringLengthTooLong' => 'Format label should be no more than 100 characters long' 
								),
							),
						),
					),
				));
				
				$this->inputFilter = $inputFilter;
			}
			
			return $this->inputFilter;
		}
	}
?>

Notice that for the message required, we have to do a funky thing to tell it the message, versus being able to refer to it with a nice catch name like we can with stringLengthTooLong. I could find no explanation for this, only that this is how you do it. And it does work, so yay.

Finally, to display our new messages nicely and to make sure we differentiate between "yay good" and "no bad" messages, I added this to the top of my layout.phtml file to set the options on our flashMessenger:

<?php
	$flash = $this->flashMessenger();
	$flash->setMessageOpenFormat('<div%s>
						<ul>
							<li>')
		->setMessageSeparatorString('</li>
							<li>')
		->setMessageCloseString('</li>
						</ul>
					</div>
		');
?>

For the curious, the tabbing/funky spacing is so it looks decent when doing view source. A minor thing, I know, but I hate doing view source and everything is just shoved all together on one line. It isn't totally pretty, but significantly better than it was before I added the tweaks. The %s by the div will auto populate in the classes attribute with any classes passed in when the render function is called.

Further down, between where I had my pageTitle and my Content displayed, I added two lines to show any messages.

<h1 class="pageTitle"><?php echo $this->pageTitle; ?></h1>
<?php 
	echo $flash->render('error',   array('message', 'error'));
	echo $flash->render('success', array('message', 'success'));
?>
<?php echo $this->content; ?>

The array sets the aforementioned CSS classes to use our div, so it alters the appearance based on whether it was a success or not. Neither set of messages will print out unless they have content and, theoretically, we could have both success and error messages showing at the same time. This can be useful for processes where one part failing does not mean the rest is not committed. And now we have our nice success and error messages.

 

The last thing I need for this section, for now at least, is the delete function. In the tutorial it has you go to a confirmation page, but I've never really been fond of that for a simple yes/no confirmation, so instead we'll do a dialog confirmation. During this process I discovered that in jQueryUI 1.10.x, the modal overlays for dialogs no longer work right – they stick a weird stripe in the middle of it. The few Google results I could find said it was from not "stacking" correctly with no actual explanation on WTF that means. For now I just switched the code back to using 1.9.2 until I can spend time looking up the stacking thing.

The second issue was how to inject the URL into the JS. In Mach-II apps we usually used to BuildURL to inject directly so if we changed configurations we didn't have to hunt down a bunch of manual links. Unfortunately, the same helpfulness that I stumbled upon earlier about it not passing any parameters not configured in the route also seems to keep it from creating a URL if I don't properly give it the value for a formatid. So for now I went ahead and manually made part of the link.

First I modified my index.phtml file to set up the delete link and add the delete dialog.

<?php
	$title = 'Formats';
	$this->headTitle($title);
	$this->layout()->pageTitle = $title;
	$this->layout()->needsDataTables = true;
?>

<div class="smallSortedTableWrapper">
	<table class="sortedTable">
	<thead>
		<tr>
			<th>ID</th>
			<th>Label</th>
			<th> </th>
		</tr>
	</thead>
	<tbody>
		<?php foreach ($formats as $format) : ?>
		<tr>
			<td><?php echo $this->escapeHtml($format->formatid);?></td>
			<td><?php echo $this->escapeHtml($format->formatlabel);?></td>
			<td>
				<a href="<?php echo $this->url('formats', array('action'=>'edit', 'formatid' => $format->formatid));?>">Edit</a> ~ 
				<span class="pseudoLink deleteLink" FormatID="<?php echo $this->escapeHtml($format->formatid);?>" FormatLabel="<?php echo $this->escapeHtml($format->formatlabel);?>">Delete</span>
			</td>
		</tr>
		<?php endforeach; ?>
	</tbody>
	</table>
	<p class="alignRight tableFooter"><a href="<?php echo $this->url('formats', array('action'=>'add'));?>">Add new format</a></p>
</div>
<div id="confirmDelete" title="Delete Format" class="helpDialogs">
	<p>Are you certain you wish to delete the "<span id="formatNameHere"></span>" format?</p>
	<p>Note: Only unused formats can be deleted.</p>
</div>

<?php $this->inlineScript()->captureStart(); ?>
	$(document).ready(function(){
		$(".sortedTable").dataTable({
			"bStateSave": false,
			"bJQueryUI": true,
			"bFilter": false,
			"bInfo": false,
			"bPaginate": false,
			"aoColumnDefs": [
				{ "sClass": "alignCenter", "bSortable": false, "aTargets": [ 2 ] }
			]
		});
		$(".sortedTable").on("click", '.deleteLink', function(){
			var FormatID = $(this).attr("FormatID");
			var FormatLabel = $(this).attr("FormatLabel");
			var linkToGoTo = '<?php echo $this->url('formats');?>delete/' + FormatID;
			
			$("#formatNameHere").html(FormatLabel);
			
			$("#confirmDelete").dialog({
				resizable: false,
				width: 400,
				height: 275,
				modal: true,
				buttons: {
					 "Yes, Delete It": function() {
						window.location.href = linkToGoTo;
					 },
					 "No, Go Back": function() {
						$( this ).dialog( "close");
					 }
				}
			});
		});

	});
<?php $this->inlineScript()->captureEnd(); ?>

Notice I also changed up my inlineScript()->captureStart() section a bit to remove the heredoc use. This was done so I could drop some PHP into to JS. If you don't need to do that, the heredoc works just fine, though I admit, this is a little more readable to me than that (and it doesn't mess up my syntax color coding as much LOL)

For the most part, I've not been including any of the CSS stuff here since I figure you guy can figure it out as most of it is pretty basic. In case anyone is curious, though, two styles I did add for this to my style sheet are for the pseudoLink and helpDialogs classes. The former includes having the link look and act like a regular link, including changing over the pointer, for better usability. helpDialogs auto hides the dialog div, while the .ui-widget.overlay overwrites jQueryUI's styling to make their overlay a bit darker. I don't like my modals too light 🙂

.pseudoLink {
	text-decoration: underline; 
	color: #00F; 
	cursor: pointer;
}
.helpDialogs {
	display: none;
}
.ui-widget-overlay {
	opacity: .6;
	filter: alpha(opacity=60);
}

Then in my FormatsController.php, I populated my deleteAction. Since I broke from the tutorial method here, I had to change up this code a bit.

public function deleteAction() {
	$formatid = (int) $this->params()->fromRoute('formatid', 0);
	if (!$formatid) {
		return $this->redirect()->toRoute('formats');
	}
	
	try {
		$formats = $this->getFormatsTable()->getFormats($formatid);
	}
	catch (\Exception $ex) {
		$this->flashMessenger()->setNamespace("error")->addMessage('Format not found; was it already deleted?!');
		return $this->redirect()->toRoute('formats', array(
			'action' => 'index'
		));
	}
	
	$request = $this->getRequest();
	
	if ($request->isGet()) {
		$this->getFormatsTable()->deleteFormats($formatid);
		$this->flashMessenger()->setNamespace("success")->addMessage('Format deleted successfully!');
	
		return $this->redirect()->toRoute('formats');
	}
	
	return array(
		'formatid'    => $formatid,
		'formats' => $this->getFormatsTable()->getFormats($formatid)
	);
}

The dialog confirmation does a straight URL redirect, so now the controller looks for a get instead of a post. I also removed the unnecessary confirmation variable, since it isn't a two page process now. Obviously, in a full version of this application, all of this add/edit/listing screens would be behind an authenticated section. If this were a public facing page, I would add greater checks on things to ensure it was a valid and allowed call.

With that, my basic formats "crud' is complete. I can now view a list of formats, add new ones, edit existing ones, and delete them. Next, I'm going to go ahead and basically repeat these steps for the other basic tables of the site, i.e. genres, studios, and series. In my next post, I'll probably look at the series stuff a bit more, as it has a few more fields and when editing it, I want to be able to see what titles I have in that series and maybe be able to add/remove titles. 🙂

Thus far I would say that while there is still the frustration of learning something new, and at times I want to beat whoever made the docs with some wet noodles, I'm definitely liking Zend way way way more than I did CakePHP.