At a Glance
This chapter builds a proper Zend Framework application from scratch, covering project directory structure, controller creation, view scripts, routing configuration, and the request flow from URL to rendered output.
What Changed Since Zend Framework 1
Modern PHP frameworks like Laravel and Symfony provide scaffolding commands that generate project structure automatically. The manual setup process in ZF1 forces you to understand every piece of the puzzle, which remains valuable even if you never touch ZF1 directly. The controller-action-view pattern is essentially identical across all major PHP MVC frameworks.
Every PHP framework tutorial starts with “Hello World.” Most of them stop there, which teaches you almost nothing about how to build a real application. This chapter goes further. As part of the Survive The Deep End: PHP and Zend Framework book, it walks through the full project directory structure, controller creation, view script wiring, routing configuration, and request lifecycle that ZF1 uses to turn a URL into rendered HTML. If you have worked through the previous installation chapter and want to understand how controllers dispatch actions, how view scripts resolve by convention, and where common first-project mistakes happen, this is the place to start. The patterns here - directory layout, front controller routing, action methods returning view data - apply almost identically to every MVC framework you will encounter in PHP.
The ZF1 Project Directory Structure
Zend Framework expects a specific directory layout. This is not optional decoration. The framework’s autoloader, the front controller’s module resolution, and the view renderer all depend on files being in the right place.
A minimal project looks like this:
1 | project/ |
The public/ directory is your document root. Every HTTP request hits public/index.php, which bootstraps the application and dispatches through the front controller. The application/ directory holds your controllers, views, models, and configuration. The library/ directory is where the Zend Framework library itself lives (or any other third-party code).
This separation matters. Keeping application code outside the document root means PHP files cannot be accessed directly by URL. It is a basic security measure, and one that many beginners skip by dumping everything into a single folder.
The Bootstrap and Front Controller
The entry point is public/index.php. Its job is minimal: define paths, set up the autoloader, and hand off to the bootstrap.
1 |
|
Zend_Application reads application.ini, runs the bootstrap, and starts the front controller. The front controller parses the URL, determines which controller and action to call, dispatches the request, and renders the response. You do not call controller methods directly. The framework does that for you based on the URL.
Creating Your First Controller
Controllers in ZF1 extend Zend_Controller_Action. Each public method ending in Action is a routable action.
1 |
|
The indexAction method handles requests to / and /index/index. The aboutAction method handles /index/about. Notice that the controller does not echo anything. It assigns data to the view object. The view renderer takes care of selecting the correct template and producing output.
This is the discipline that MVC enforces. The controller decides what data the view needs. The view decides how to display it. If you start putting HTML in your controllers, you have broken the pattern and you will pay for it in maintenance cost within weeks.
View Scripts and the ViewRenderer
ZF1 uses a convention-based view renderer. When IndexController::aboutAction() runs, the framework looks for a view script at:
1 | application/views/scripts/index/about.phtml |
The path follows the pattern: views/scripts/{controller}/{action}.phtml. You do not need to configure this mapping. It happens automatically unless you override it.
A view script is plain PHP mixed with HTML:
1 | <h1> echo $this->escape($this->title); </h1> |
The $this->escape() call runs htmlspecialchars() on the value. Use it on every variable you output. XSS vulnerabilities start the moment you forget.
Routing: How URLs Map to Controllers
The default router in ZF1 uses a simple pattern:
1 | /:controller/:action/:param/value |
So /blog/edit/id/5 routes to BlogController::editAction() with the parameter id set to 5. You retrieve it in the action with:
1 | $id = $this->getRequest()->getParam('id'); |
You can define custom routes in Bootstrap.php or application.ini for cleaner URLs. For example, mapping /blog/2024/03/my-post to a specific controller and action with named parameters. But the default routing covers most simple applications without any configuration.
The Request Lifecycle
Understanding the full request flow prevents a huge number of debugging headaches. Here is what happens for a request to /blog/view/id/7:
- Apache (or Nginx) rewrites the URL to
public/index.phpvia.htaccess. index.phpbootstrapsZend_Application.- The front controller’s router parses the URL into controller=
blog, action=view, params={id: 7}. - The dispatcher locates
BlogControllerand callsviewAction(). - The action method runs, assigns data to
$this->view. - The view renderer loads
views/scripts/blog/view.phtmland renders it. - The response object collects the output and sends headers and body to the client.
If anything goes wrong - missing controller class, uncaught exception, missing view script - the error handler plugin catches it and dispatches to ErrorController::errorAction().
Error Handling
ZF1 ships with an error handler plugin that catches exceptions thrown during dispatch. Your ErrorController receives the exception and decides how to display it:
1 |
|
In production, you hide exception details. In development, you display them. The application.ini environment setting controls which configuration block loads, so you can toggle error display without touching code.
Common Pitfalls
After a decade of seeing ZF1 projects in various states of disrepair, these are the mistakes that come up repeatedly:
Forgetting the document root setting. If your webserver points to the project root instead of public/, every PHP file in application/ is directly accessible via URL. This is a security hole.
Putting logic in view scripts. View scripts should contain display logic only. Database queries in .phtml files create untestable, unmaintainable spaghetti. If you need data, get it in the controller or a model.
Ignoring the autoloader. ZF1’s autoloader follows PEAR naming conventions. If your class is Application_Model_User, it must live at application/models/User.php. Get the mapping wrong and you get cryptic “class not found” errors with no useful stack trace.
Not setting error reporting in development. ZF1 suppresses many warnings by default. Run development with error_reporting(E_ALL | E_STRICT) and display_errors = On. You want to see every notice and warning during development.
Frequently Asked Questions
Do I need Zend_Tool to create a project?
No. Zend_Tool (zf command) can generate the directory structure and boilerplate files, but it is not required. You can create every file by hand. In fact, doing it manually the first time gives you a better understanding of what each file does and why it exists.
Can I use a different directory layout?
Technically yes, but it requires overriding the front controller’s module directory, the view renderer’s script paths, and the autoloader’s class map. For a standard single-module application, the default layout is the right choice. Fight the framework on conventions and you will waste hours on configuration that adds no value.
Why does my action render a blank page?
The most common cause is a missing view script. If IndexController::testAction() runs but views/scripts/index/test.phtml does not exist, ZF1 may render nothing or throw an exception depending on your error settings. Check the view script path first.
How does ZF1 routing compare to modern frameworks?
The default /:controller/:action pattern is simpler than Laravel’s explicit route definitions or Symfony’s annotated routes. ZF1 trades flexibility for convention. You can add custom routes for anything the default pattern does not cover, but most ZF1 applications relied heavily on the convention-based defaults.
Related Reading
- Introduction - Background and overview of the framework
- Installing the Zend Framework - Getting ZF1 set up before building your first application
- Developing a Blogging Application - Applying this project structure to a real CRUD application
- The Model - Understanding the layer this chapter deliberately avoided