Chapter 5: Models & Database (Zend\Db) in Zend Framework 3
✅ Introduction to Models
The Model in MVC handles business logic and data operations. In Zend Framework 3, models usually interact with the database using Zend\Db. This component provides tools for SQL queries, table gateways, and object hydrators.
- Models represent the data and rules of the application.
- Models communicate with the database using
Zend\Db\Adapter. - Recommended to use TableGateway for CRUD operations.
✅ Database Adapter Setup
First, configure the database connection in global.php:
// File: config/autoload/global.php
return [
'db' => [
'driver' => 'Pdo_Mysql',
'database' => 'zf3tutorial',
'username' => 'root',
'password' => '',
'hostname' => 'localhost',
],
'service_manager' => [
'factories' => [
Zend\Db\Adapter\Adapter::class => Zend\Db\Adapter\AdapterServiceFactory::class,
],
],
];
✅ Creating the Model
Let’s create a simple model for a posts table.
// File: module/Blog/src/Model/Post.php
namespace Blog\Model;
class Post
{
public $id;
public $title;
public $content;
public function exchangeArray(array $data)
{
$this->id = !empty($data['id']) ? $data['id'] : null;
$this->title = !empty($data['title']) ? $data['title'] : null;
$this->content = !empty($data['content']) ? $data['content'] : null;
}
}
✅ Table Gateway Class
The TableGateway provides CRUD operations for a specific table.
// File: module/Blog/src/Model/PostTable.php
namespace Blog\Model;
use RuntimeException;
use Zend\Db\TableGateway\TableGatewayInterface;
class PostTable
{
private $tableGateway;
public function __construct(TableGatewayInterface $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
return $this->tableGateway->select();
}
public function getPost($id)
{
$id = (int) $id;
$row = $this->tableGateway->select(['id' => $id])->current();
if (!$row) {
throw new RuntimeException(sprintf(
'Could not find post with id %d', $id
));
}
return $row;
}
public function savePost(Post $post)
{
$data = [
'title' => $post->title,
'content' => $post->content,
];
$id = (int) $post->id;
if ($id === 0) {
$this->tableGateway->insert($data);
return;
}
if (!$this->getPost($id)) {
throw new RuntimeException("Post id $id does not exist");
}
$this->tableGateway->update($data, ['id' => $id]);
}
public function deletePost($id)
{
$this->tableGateway->delete(['id' => (int) $id]);
}
}
✅ Registering the Service
We now register PostTable in the Module.php file:
<?php
namespace Blog;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
use Laminas\ModuleManager\Feature\ServiceProviderInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Blog\Model\Post; use Blog\Model\PostTable;
class Module implements ConfigProviderInterface, ServiceProviderInterface
{
public function getConfig() { return include __DIR__ . '/../config/module.config.php'; }
public function getServiceConfig() { return [ 'factories' => [
// Register PostTable
PostTable::class => function($container) {
$tableGateway = $container->get('PostTableGateway');
return new PostTable($tableGateway);
},
// Register TableGateway for "posts"
'PostTableGateway' => function($container) {
$dbAdapter = $container->get('Laminas\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Post());
return new TableGateway('posts', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
}
✅ Using the Model in Controllers
// File: module/Blog/src/Controller/PostController.php
namespace Blog\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Blog\Model\PostTable;
class PostController extends AbstractActionController
{
private $postTable;
public function __construct(PostTable $postTable)
{
$this->postTable = $postTable;
}
public function indexAction()
{
return new ViewModel([
'posts' => $this->postTable->fetchAll(),
]);
}
}
✅ Displaying Data in Views
// File: module/Blog/view/blog/post/index.phtml
<h2>Posts</h2>
<ul>
<?php foreach ($this->posts as $post): ?>
<li>
<strong><?= $this->escapeHtml($post->title) ?></strong>
<p><?= $this->escapeHtml($post->content) ?></p>
</li>
<?php endforeach; ?>
</ul>
✅ Best Practices for Models
- Keep business logic in the model, not in controllers or views.
- Use
TableGatewayfor structured CRUD operations. - Validate and sanitize input before saving to the database.
- Separate database logic into service classes for scalability.
✅ Exercises
- Create a
usersmodel and connect it to auserstable. - Add CRUD operations for the
usersmodel similar toPost. - Update the view to display user data from the database.
- Try to handle errors when fetching a non-existing record.
