Zend Framework Guide
Introduction to Zend Framework
Zend Framework (now known as Laminas Project) is an enterprise-grade PHP framework focused on simplicity, best practices, and corporate-friendly licensing. It provides a collection of professionally-crafted components that can be used independently or together.
Installation and Setup
Installing Laminas MVC
# Create new Laminas MVC application
composer create-project laminas/laminas-mvc-skeleton my-app
# Navigate to project directory
cd my-app
# Install dependencies
composer install
# Set up development server
composer serve
Manual Installation
# Install specific components
composer require laminas/laminas-mvc
composer require laminas/laminas-db
composer require laminas/laminas-form
composer require laminas/laminas-authentication
Project Structure
my-app/
├── config/
│ ├── application.config.php
│ ├── autoload/
│ │ ├── global.php
│ │ └── local.php.dist
│ └── modules.config.php
├── module/
│ └── Application/
│ ├── config/
│ │ └── module.config.php
│ ├── src/
│ │ ├── Controller/
│ │ ├── Model/
│ │ └── Service/
│ └── view/
├── public/
│ ├── index.php
│ ├── css/
│ └── js/
├── vendor/
└── composer.json
Configuration
Application Configuration
<?php
// config/application.config.php
return [
'modules' => [
'Laminas\Router',
'Laminas\Validator',
'Application',
'User',
'Blog'
],
'module_listener_options' => [
'module_paths' => [
'./module',
'./vendor'
],
'config_glob_paths' => [
realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php',
],
'config_cache_enabled' => false,
'config_cache_key' => 'application.config.cache',
'module_map_cache_enabled' => false,
'module_map_cache_key' => 'application.module.cache',
'cache_dir' => 'data/cache/'
]
];
?>
Module Configuration
<?php
// module/Application/config/module.config.php
namespace Application;
use Laminas\Router\Http\Literal;
use Laminas\Router\Http\Segment;
return [
'router' => [
'routes' => [
'home' => [
'type' => Literal::class,
'options' => [
'route' => '/',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
'user' => [
'type' => Segment::class,
'options' => [
'route' => '/user[/:action[/:id]]',
'constraints' => [
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
],
'defaults' => [
'controller' => Controller\UserController::class,
'action' => 'index',
],
],
],
],
],
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
Controller\UserController::class => Controller\Factory\UserControllerFactory::class,
],
],
'view_manager' => [
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => [
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
],
'template_path_stack' => [
__DIR__ . '/../view',
],
],
'service_manager' => [
'factories' => [
'Laminas\Db\Adapter\Adapter' => 'Laminas\Db\Adapter\AdapterServiceFactory',
],
],
];
?>
Controllers
Basic Controller
<?php
// module/Application/src/Controller/IndexController.php
namespace Application\Controller;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
public function indexAction()
{
return new ViewModel([
'message' => 'Welcome to Laminas Framework!'
]);
}
public function aboutAction()
{
$this->layout('layout/simple');
return new ViewModel([
'title' => 'About Us',
'content' => 'This is the about page.'
]);
}
}
?>
Controller with Dependencies
<?php
// module/Application/src/Controller/UserController.php
namespace Application\Controller;
use Application\Service\UserService;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Laminas\View\Model\JsonModel;
class UserController extends AbstractActionController
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function indexAction()
{
$users = $this->userService->getAllUsers();
return new ViewModel([
'users' => $users
]);
}
public function viewAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('user');
}
$user = $this->userService->getUser($id);
if (!$user) {
return $this->notFoundAction();
}
return new ViewModel([
'user' => $user
]);
}
public function apiAction()
{
$users = $this->userService->getAllUsers();
return new JsonModel([
'status' => 'success',
'data' => $users
]);
}
}
// Controller Factory
namespace Application\Controller\Factory;
use Application\Controller\UserController;
use Application\Service\UserService;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;
class UserControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$userService = $container->get(UserService::class);
return new UserController($userService);
}
}
?>
Models and Database
Database Configuration
<?php
// config/autoload/global.php
return [
'db' => [
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=myapp;host=localhost',
'driver_options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
],
],
];
// config/autoload/local.php
return [
'db' => [
'username' => 'your_username',
'password' => 'your_password',
],
];
?>
Table Gateway Pattern
<?php
// module/Application/src/Model/UserTable.php
namespace Application\Model;
use Laminas\Db\TableGateway\TableGatewayInterface;
use Laminas\Db\Sql\Select;
use Laminas\Paginator\Adapter\DbSelect;
use Laminas\Paginator\Paginator;
class UserTable
{
private TableGatewayInterface $tableGateway;
public function __construct(TableGatewayInterface $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll($paginated = false)
{
if ($paginated) {
return $this->fetchPaginatedResults();
}
return $this->tableGateway->select();
}
public function getUser($id)
{
$id = (int) $id;
$rowset = $this->tableGateway->select(['id' => $id]);
$row = $rowset->current();
if (!$row) {
throw new RuntimeException(sprintf(
'Could not find user with identifier %d',
$id
));
}
return $row;
}
public function saveUser(User $user)
{
$data = [
'name' => $user->name,
'email' => $user->email,
'active' => $user->active ? 1 : 0,
];
$id = (int) $user->id;
if ($id === 0) {
$this->tableGateway->insert($data);
return $this->tableGateway->getLastInsertValue();
}
try {
$this->getUser($id);
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf(
'Cannot update user with identifier %d; does not exist',
$id
));
}
$this->tableGateway->update($data, ['id' => $id]);
return $id;
}
public function deleteUser($id)
{
$this->tableGateway->delete(['id' => (int) $id]);
}
private function fetchPaginatedResults()
{
$select = new Select('users');
$adapter = new DbSelect($select, $this->tableGateway->getAdapter());
return new Paginator($adapter);
}
}
// User Entity
namespace Application\Model;
class User
{
public $id;
public $name;
public $email;
public $active;
public function exchangeArray(array $data)
{
$this->id = !empty($data['id']) ? $data['id'] : null;
$this->name = !empty($data['name']) ? $data['name'] : null;
$this->email = !empty($data['email']) ? $data['email'] : null;
$this->active = !empty($data['active']) ? $data['active'] : false;
}
public function getArrayCopy()
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'active' => $this->active,
];
}
}
?>
Services and Dependency Injection
Service Layer
<?php
// module/Application/src/Service/UserService.php
namespace Application\Service;
use Application\Model\UserTable;
use Application\Model\User;
class UserService
{
private UserTable $userTable;
public function __construct(UserTable $userTable)
{
$this->userTable = $userTable;
}
public function getAllUsers()
{
return $this->userTable->fetchAll();
}
public function getUser($id)
{
return $this->userTable->getUser($id);
}
public function createUser(array $data)
{
$user = new User();
$user->exchangeArray($data);
// Validation logic here
$this->validateUser($user);
return $this->userTable->saveUser($user);
}
public function updateUser($id, array $data)
{
$user = $this->userTable->getUser($id);
$user->exchangeArray(array_merge($user->getArrayCopy(), $data));
$this->validateUser($user);
return $this->userTable->saveUser($user);
}
public function deleteUser($id)
{
$this->userTable->deleteUser($id);
}
private function validateUser(User $user)
{
if (empty($user->name)) {
throw new InvalidArgumentException('Name is required');
}
if (empty($user->email) || !filter_var($user->email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Valid email is required');
}
}
}
?>
Service Manager Configuration
<?php
// module/Application/config/module.config.php (add to existing config)
'service_manager' => [
'factories' => [
UserService::class => UserServiceFactory::class,
UserTable::class => UserTableFactory::class,
],
],
// Service Factories
namespace Application\Service\Factory;
use Application\Model\UserTable;
use Application\Service\UserService;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;
class UserServiceFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$userTable = $container->get(UserTable::class);
return new UserService($userTable);
}
}
class UserTableFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$dbAdapter = $container->get('Laminas\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new User());
return new TableGateway('users', $dbAdapter, null, $resultSetPrototype);
}
}
?>
Forms and Validation
Form Creation
<?php
// module/Application/src/Form/UserForm.php
namespace Application\Form;
use Laminas\Form\Form;
use Laminas\Form\Element;
use Laminas\InputFilter\InputFilterProviderInterface;
class UserForm extends Form implements InputFilterProviderInterface
{
public function __construct($name = null)
{
parent::__construct('user');
$this->add([
'name' => 'id',
'type' => Element\Hidden::class,
]);
$this->add([
'name' => 'name',
'type' => Element\Text::class,
'options' => [
'label' => 'Name',
],
'attributes' => [
'required' => true,
'class' => 'form-control',
],
]);
$this->add([
'name' => 'email',
'type' => Element\Email::class,
'options' => [
'label' => 'Email',
],
'attributes' => [
'required' => true,
'class' => 'form-control',
],
]);
$this->add([
'name' => 'active',
'type' => Element\Checkbox::class,
'options' => [
'label' => 'Active',
'use_hidden_element' => true,
'checked_value' => '1',
'unchecked_value' => '0',
],
]);
$this->add([
'name' => 'submit',
'type' => Element\Submit::class,
'attributes' => [
'value' => 'Save',
'class' => 'btn btn-primary',
],
]);
}
public function getInputFilterSpecification()
{
return [
'name' => [
'required' => true,
'filters' => [
['name' => 'StringTrim'],
['name' => 'StripTags'],
],
'validators' => [
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 2,
'max' => 100,
],
],
],
],
'email' => [
'required' => true,
'filters' => [
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'EmailAddress'],
],
],
];
}
}
?>
Authentication and Authorization
Authentication Setup
<?php
// module/Application/src/Service/AuthService.php
namespace Application\Service;
use Laminas\Authentication\AuthenticationService;
use Laminas\Authentication\Storage\Session as SessionStorage;
use Laminas\Authentication\Adapter\DbTable\CredentialTreatmentAdapter;
class AuthService
{
private AuthenticationService $authService;
public function __construct($dbAdapter)
{
$this->authService = new AuthenticationService();
// Set up session storage
$this->authService->setStorage(new SessionStorage('auth'));
// Set up database adapter
$authAdapter = new CredentialTreatmentAdapter(
$dbAdapter,
'users',
'email',
'password',
'MD5(?)'
);
$this->authService->setAdapter($authAdapter);
}
public function login($email, $password)
{
$this->authService->getAdapter()
->setIdentity($email)
->setCredential($password);
$result = $this->authService->authenticate();
return $result->isValid();
}
public function logout()
{
$this->authService->clearIdentity();
}
public function hasIdentity()
{
return $this->authService->hasIdentity();
}
public function getIdentity()
{
return $this->authService->getIdentity();
}
}
?>
Modules
Creating a Module
<?php
// module/Blog/Module.php
namespace Blog;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
use Laminas\ModuleManager\Feature\AutoloaderProviderInterface;
class Module implements ConfigProviderInterface, AutoloaderProviderInterface
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return [
'Laminas\Loader\StandardAutoloader' => [
'namespaces' => [
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
],
],
];
}
public function onBootstrap($e)
{
// Module initialization code
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach('route', [$this, 'onRoute'], -100);
}
public function onRoute($e)
{
// Route-specific logic
}
}
?>
Testing
Unit Testing
<?php
// module/Application/test/Controller/UserControllerTest.php
namespace ApplicationTest\Controller;
use Application\Controller\UserController;
use Application\Service\UserService;
use Laminas\Stdlib\ArrayUtils;
use Laminas\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class UserControllerTest extends AbstractHttpControllerTestCase
{
protected $traceError = true;
protected function setUp(): void
{
$configOverrides = [];
$this->setApplicationConfig(ArrayUtils::merge(
include __DIR__ . '/../../../../config/application.config.php',
$configOverrides
));
parent::setUp();
}
public function testIndexActionCanBeAccessed()
{
$this->dispatch('/user');
$this->assertResponseStatusCode(200);
$this->assertModuleName('Application');
$this->assertControllerName(UserController::class);
$this->assertActionName('index');
}
public function testUserCanBeViewed()
{
$this->dispatch('/user/view/1');
$this->assertResponseStatusCode(200);
$this->assertModuleName('Application');
$this->assertControllerName(UserController::class);
$this->assertActionName('view');
}
public function testInvalidUserReturns404()
{
$this->dispatch('/user/view/9999');
$this->assertResponseStatusCode(404);
}
}
?>
Best Practices
Performance Optimization
<?php
// Enable OpCache configuration
// config/autoload/global.php
return [
'module_listener_options' => [
'config_cache_enabled' => true,
'config_cache_key' => 'application.config.cache',
'module_map_cache_enabled' => true,
'module_map_cache_key' => 'application.module.cache',
'cache_dir' => 'data/cache/',
],
// Database connection pooling
'db' => [
'driver_options' => [
PDO::ATTR_PERSISTENT => true,
],
],
];
?>
Security Best Practices
<?php
// CSRF Protection
use Laminas\Form\Element\Csrf;
$form->add([
'name' => 'csrf',
'type' => Csrf::class,
'options' => [
'csrf_options' => [
'timeout' => 600
]
]
]);
// Input Filtering
use Laminas\Filter\StringTrim;
use Laminas\Filter\StripTags;
use Laminas\Validator\StringLength;
$inputFilter->add([
'name' => 'content',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 1000,
],
],
],
]);
?>
Laminas Framework provides a robust, enterprise-grade foundation for PHP applications with its modular architecture, comprehensive component library, and focus on best practices and standards compliance.