📜 ⬆️ ⬇️

Introduction to the Zend Framework (continued)

We continue the story of the Zend Framework. In the first part of the article, the concept of the MVC software architecture was described, the structure of a typical web application based on the Zend Framework was considered, and a demo implementation of the controller and a view based on it was made. In the second part, the theme of the model will be revealed and an example of the application's interaction with the database is given.

Posted by: Rob Allen, akrabat.com
Original: akrabat.com/zend-framework-tutorial
Translation: Alexander Musayev, musayev.com

Database


Now that the control part of our application and the visualization code are separated, it’s time to do a model. Remember that a model is a basic part of an application that implements its main functions. Therefore, in our case, the model performs work with the database. We use the Zend Framework class Zend_Db_Table , designed to search, insert, update and delete records in a database table.
')

Customization


To use Zend_Db_Table , you will need to tell him which database, what username and password he will use. Taking into account that this information is preferably not to be hammered into the code, we will use the configuration file for its storage.

Zend Framework provides for this purpose the Zend_Config class, which provides flexible object-oriented access to configuration files in INI and XML formats. Stop your choice on the ini file:

zf-tutorial/application/config.ini:

 [general] db.adapter = PDO_MYSQL db.config.host = localhost db.config.username = rob db.config.password = 123456 db.config.dbname = zftest 

(Of course, you will need to use your own database access parameters, not the ones shown in the example.)

Using Zend_Config will be very simple:

 $config = new Zend_Config_Ini('config.ini', 'section'); 

In this case, Zend_Config_Ini loads one section from the INI file (in the same way, if necessary, you can load any other section). The possibility of naming sections is implemented so that unnecessary data is not needlessly loaded. Zend_Config_Ini uses a dot in the parameter names as a hierarchical separator, making it possible to group related parameters. In our config.ini file, the host , username , password and dbname parameters will be grouped in the $config->db->config object.

We will load the configuration file from the bootstrap file ( index.php ):

zf-tutorial/index.php:

 ... Zend_Loader::loadClass('Zend_Controller_Front'); Zend_Loader::loadClass('Zend_Config_Ini'); Zend_Loader::loadClass('Zend_Registry'); // load configuration $config = new Zend_Config_Ini('./application/config.ini', 'general'); $registry = Zend_Registry::getInstance(); $registry->set('config', $config); // setup controller ... 

After loading the necessary classes for work ( Zend_Config_Ini and Zend_Registry ), a section of the application/config.ini configuration file is loaded into the $config object under the name general . Next, the $config object is included in the registry, which provides access to it from the entire application.

Note: In this example, there is no real need to store $config in the registry. This is done as an example of the operation of a “real” application, in the configuration file of which you may have to store something more than the database access parameters. When using the registry, also note that the data in it is available at the global level, and that if used carelessly, this can cause potential conflict situations within the application.

Using Zend_Db_Table

In order to use the Zend_Db_Table class, we will need to pass into it the database access parameter that was just loaded. To do this, you need to create a Zend_Db object and register it with the Zend_Db_Table::setDefaultAdapter() function. Below is the corresponding code snippet in the primary boot file:

zf-tutorial/index.php:

 ... Zend_Loader::loadClass('Zend_Controller_Front'); Zend_Loader::loadClass('Zend_Config_Ini'); Zend_Loader::loadClass('Zend_Registry'); <b>Zend_Loader::loadClass('Zend_Db'); Zend_Loader::loadClass('Zend_Db_Table');</b> // load configuration $config = new Zend_Config_Ini('./application/config.ini', 'general'); $registry = Zend_Registry::getInstance(); $registry->set('config', $config); <b>// setup database $db = Zend_Db::factory($config->db->adapter, $config->db->config->toArray()); Zend_Db_Table::setDefaultAdapter($db);</b> // setup controller ... 

Creating a table

We will use the MySQL database, so the SQL query for creating the table will look like this:

 CREATE TABLE album ( id int(11) NOT NULL auto_increment, artist varchar(100) NOT NULL, title varchar(100) NOT NULL, PRIMARY KEY (id) ); 

This query can be executed through any MySQL client, for example, phpMyAdmin or a standard console utility.

Adding a test record

Add a few test records to the table to check the functionality of the main page, on which they should later be displayed.

 INSERT INTO album (artist, title) VALUES ('James Morrison', 'Undiscovered'), ('Snow Patrol', 'Eyes Open'); 

Model


Zend_Db_Table is an abstract class, so on its basis it is necessary to create a heir class specialized in our task. The name of a new class does not matter in principle, but such classes should be called like the corresponding DB tables (this will increase the level of self-documenting of the code). Thus, for our album table, the class name will be Album .

In order to pass to Zend_Db_Table name of the table that it will manage, you must assign this value to the protected property of the $_name class. It should be noted that in the Zend_Db_Table class, by default, a key auto-increment table field with the name id always used. If necessary, the value of the name of this field can also be changed.

The Album class will be stored in the model directory.

zf-tutorial/application/models/Album.php:

 <?php class Album extends Zend_Db_Table { protected $_name = 'album'; } 

Nothing complicated, is it? Given that our needs in this case are quite small, the basic functionality of Zend_Db_Table will be enough to fully satisfy them. If, in your tasks, you need to implement more complex operations of interaction with the database, then the class of the model is exactly the place where the corresponding code should be placed.

Album list


Now that we have configured the database, we can proceed to the main task of the application and display several entries. This should be implemented in the IndexController class. Each action function inside IndexController interacts with the album table through the class Album . Therefore, it makes sense to load it when the controller is initialized inside the init() function.

zf-tutorial/application/controllers/IndexController.php:

 ... function init() { $this->view->baseUrl = $this->_request->getBaseUrl(); Zend_Loader::loadClass('Album'); } ... 

Note: This code snippet shows an example of using the Zend_Loader::loadClass() method to load non-standard classes. This works, because the model directory (in which the loadable Albums class is stored) was added by us to include_path .

Create a list of albums from the database using indexAction() :

zf-tutorial/application/controllers/IndexController.php:

 ... function indexAction() { $this->view->title = "My Albums"; $album = new Album(); $this->view->albums = $album->fetchAll(); } ... 

The fetchAll() function returns a Zend_Db_Table_Rowset object, which will allow us to generate a list of all the records in a template file of the form:
zf-tutorial/application/views/scripts/index/index.phtml:

 <?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <p><a href="<?php echo $this->baseUrl; ?>/index/add">Add new album</a></p> <table> <tr> <th>Title</th> <th>Artist</th> <th> </th> </tr> <?php foreach($this->albums as $album) : ?> <tr> <td><?php echo $this->escape($album->title);?></td> <td><?php echo $this->escape($album->artist);?></td> <td> <a href="<?php echo $this->baseUrl; ?>/index/edit/id/<?php echo $album->id;?>">Edit</a> <a href="<?php echo $this->baseUrl; ?>/index/delete/id/<?php echo $album->id;?>">Delete</a> </td> </tr> <?php endforeach; ?> </table> <?php echo $this->render('footer.phtml'); ?> 

By the address localhost/zf-tutorial localhost/zf-tutorial should now display a list of our two albums.

Adding a new album


We now turn to the function of adding a new disk to the database. This task consists of two parts:

We implement the listed operations in the addAction() action addAction() :

zf-tutorial/application/controllers/IndexController.php:

 ... function addAction() { $this->view->title = "Add New Album"; if ($this->_request->isPost()) { Zend_Loader::loadClass('Zend_Filter_StripTags'); $filter = new Zend_Filter_StripTags(); $artist = $filter->filter($this->_request->getPost('artist')); $artist = trim($artist); $title = trim($filter->filter($this->_request->getPost('title'))); if ($artist != '' && $title != '') { $data = array( 'artist' => $artist, 'title' => $title, ); $album = new Album(); $album->insert($data); $this->_ redirect('/'); return; } } // set up an "empty" album $this->view->album = new stdClass(); $this->view->album->id = null; $this->view->album->artist = ''; $this->view->album->title = ''; // additional view fields required by form $this->view->action = 'add'; $this->view->buttonText = 'Add';</b> } ... 

Notice how at the beginning of the function it was determined whether there was a transfer of the data of their form. If the data from the form has been submitted, we retrieve the artist and title values, and process them with the HTML tag filter Zend_Filter_StripTags . Further, if the rows have a non-empty value, we use the Album() class to add a new record to the database table. After adding an album, the user is redirected back to the main page using the _redirect() controller method.

The last thing you need to do is prepare an HTML form for the view template. Looking ahead, it can be noted that the form for editing records will look identical to the form for adding them. Therefore, we use a common template file ( _form.html ), which will be used in add.phtml and edit.phtml :

Template page for adding a new album:

zf-tutorial/application/views/scripts/index/add.phtml:

 <?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php echo $this->render('index/_form.phtml'); ?> <?php echo $this->render('footer.phtml'); ?> 

zf-tutorial/application/views/scripts/index/_form.phtml:

 <form action="<?php echo $this->baseUrl ?>/index/<?php echo $this->action; ?>" method="post"> <div> <label for="artist">Artist</label> <input type="text" name="artist" value="<?php echo $this->escape(trim($this->album->artist));?>"/> </div> <div> <label for="title">Title</label> <input type="text" name="title" value="<?php echo $this->escape($this->album->title);?>"/> </div> <div id="formbutton"> <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" /> <input type="submit" name="add" value="<?php echo $this->escape($this->buttonText); ?>" /> </div> </form> 

It turned out quite simple code. Given that we intend to use _form.phtml also when editing records, we use the $this->action variable instead of hard-coding the name of the necessary action. In the same way, the variable is set to the inscription on the button to transfer data from the form.

Editing an album


Editing an album is in many respects identical to adding a new record, so the code turns out to be similar:

zf-tutorial/application/controllers/IndexController.php:

 ... function editAction() { $this->view->title = "Edit Album"; $album = new Album(); if ($this->_request->isPost()) { Zend_Loader::loadClass('Zend_Filter_StripTags'); $filter = new Zend_Filter_StripTags(); $id = (int)$this->_request->getPost('id'); $artist = $filter->filter($this->_request->getPost('artist')); $artist = trim($artist); $title = trim($filter->filter($this->_request->getPost('title'))); if ($id !== false) { if ($artist != '' && $title != '') { $data = array( 'artist' => $artist, 'title' => $title, ); $where = 'id = ' . $id; $album->update($data, $where); $this->_redirect('/'); return; } else { $this->view->album = $album->fetchRow('id='.$id); } } } else { // album id should be $params['id'] $id = (int)$this->_request->getParam('id', 0); if ($id > 0) { $this->view->album = $album->fetchRow('id='.$id); } } // additional view fields required by form $this->view->action = 'edit'; $this->view->buttonText = 'Update'; } ... 

It is worth noting that in cases where the data was not transferred to the script from the form, we can get the id parameter from the params property of the Request object using the getParam() method.

The template code is shown below:

zf-tutorial/application/views/scripts/index/edit.phtml:

 <?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <?php echo $this->render('index/_form.phtml'); ?> <?php echo $this->render('footer.phtml'); ?> 

Refactoring


Of course, your attention could not escape the fact that addAction() and editAction() very similar, and the templates for adding and editing records are generally identical. It is logical to assume the need for code refactoring. Solve this problem on your own, as an additional practical exercise.

Deleting an album


In order to complete the development of the application, you will need to add a function to delete records. We have links to remove albums opposite each item in the list. The first solution that comes to mind is to delete the records immediately after clicking on one of these links. But this is the wrong approach. Recall that according to the HTTP specification, you should not perform irreversible actions using the GET method. In such cases, it is recommended to use POST. An example of this is the work of Google Accelerator.

We must request confirmation before deleting records and delete them only after it is received. The code that implements these actions is somewhat similar to the function-actions already existing in our application:

zf-tutorial/application/controllers/IndexController.php:

 ... function deleteAction() { $this->view->title = "Delete Album"; $album = new Album(); if ($this->_request->isPost()) { Zend_Loader::loadClass('Zend_Filter_Alpha'); $filter = new Zend_Filter_Alpha(); $id = (int)$this->_request->getPost('id'); $del = $filter->filter($this->_request->getPost('del')); if ($del == 'Yes' && $id > 0) { $where = 'id = ' . $id; $rows_affected = $album->delete($where); } } else { $id = (int)$this->_request->getParam('id'); if ($id > 0) { // only render if we have an id and can find the album. $this->view->album = $album->fetchRow('id='.$id); if ($this->view->album->id > 0) { // render template automatically return; } } } // redirect back to the album list unless we have rendered the view $this->_redirect('/'); } ... 

The same method of determining the method of accessing the script and selecting the required operation (deleting the record or issuing a confirmation form) is used. In the same way as in the case of adding and editing, deletion is performed via Zend_Db_Table using the delete() method. At the end of the function, a user is redirected to a page with a list of albums. Thus, if one of the parameter _redirect() checks fails, it returns to the original page without the help of repeated access to _redirect() inside the function.

The template is a simple form:
zf-tutorial/application/views/scripts/index/delete.phtml:

 <?php echo $this->render('header.phtml'); ?> <h1><?php echo $this->escape($this->title); ?></h1> <b><?php if ($this->album) :?> <form action="<?php echo $this->baseUrl ?>/index/delete" method="post"> <p>Are you sure that you want to delete '<?php echo $this->escape($this->album->title); ?>' by '<?php echo $this->escape($this->album->artist); ?>'? </p> <div> <input type="hidden" name="id" value="<?php echo $this->album->id; ?>" /> <input type="submit" name="del" value="Yes" /> <input type="submit" name="del" value="No" /> </div> </form> <?php else: ?> <p>Cannot find album.</p> <?php endif;?></b> <?php echo $this->render('footer.phtml'); ?> 

Troubleshooting


If there are difficulties in accessing any actions other than index / index , most likely the reason is that the class router cannot correctly determine which directory your website is in. This situation may occur if the URL of the site does not match the path to its directory relative to the root directory opened for access from the network.
If the above code does not match your case, you must set the $baseURL variable to the correct value for your server:

zf-tutorial/index.php:

 ... // setup controller $frontController = Zend_Controller_Front::getInstance(); $frontController->throwExceptions(true); $frontController->setBaseUrl('/mysubdir/zf-tutorial'); $frontController->setControllerDirectory('./application/controllers'); ... 

The value /mysubdir/zf-tutorial/ will need to be replaced with the actual path to the index.php file. For example, if your URL to index.php looks like localhost/~ralle/zf-tutorial/index.php localhost/~ralle/zf-tutorial/index.php , the correct value for the $baseUrl variable is /~ralle/zf-tutorial/ .

Conclusion


With this, the construction of a simple but full-featured MVC application can be considered complete. I hope this review was helpful and informative for you. Any comments on the original text can be sent to the author of the article at rob@akrabat.com. Comments related to the Russian translation, send to musayev@yandex.ru.

This article is only a superficial review of the Zend Framework, in which there are a great many other classes besides those listed here. For detailed familiarization with them you should refer to the corresponding resources:

The translation used materials from the free encyclopedia Wikipedia.org.

Source: https://habr.com/ru/post/31173/


All Articles