1. php
  2. /frameworks
  3. /zend

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.