Zend Framework Example Shop Application

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Another overall stab - using the recent filesystem layout, and my (possible) config and index/bootstrap shell.

Code: Select all

/zfPetShop
    /config
        config.ini
    /default
        /controllers
        /models
        /views
            /filters
            /helpers
            /scripts
    /Zend
    /extends
    bootstrap.php

Code: Select all

/document_root
    /images
    /scripts
    /styles
    .htaccess
    index.php
Added to the smaller suggested system an extends, and default directory. extends to hold ZF subclasses, a default to capture the default controllers/views/models in case Modularisation is being used.

index.php:

Code: Select all

<?php

/**
 * Manually edited include path to central bootstrap.php
 */

include '/path/to/zfPetShop/bootstrap.php';
Index file is just a shell presence on the document_root which simply includes the central application's bootstrap. This basically keeps almost all the application's PHP in one place, and below the document root.

Example config.ini:

Code: Select all

; zfPetShop Bootstrap Configuration
[general]

; Global settings?
encoding = "UTF-8"
logfile =

; Bootstrap settings
bootstrap.timezone = "Europe/Dublin"
bootstrap.displayerrors = 1
bootstrap.reporting = 8191
bootstrap.renderexceptions = 1

; Database settings (change around if using Zend_Db conventions)
db.host = localhost
db.name = zfpetshop
db.username = root
db.password = passwd
db.port = 
db.type = pdomysql

; Other sections as needed
This could be XML or plain PHP - same result, an OO interface to configuration data. I haven't added production/development divisions in the data. Unless the file is manually replaced, or a build tool employed, it's likely the only other solution. Just not for database credentials...;).

bootstrap.php:

Code: Select all

<?php

/**
 * Include path setup (assuming no facility to edit php.ini)
 * Also assumed only Core ZF (or Core + copy of Incubator).
 * Left out Models for now.
 */
set_include_path(
    '.' . PATH_SEPARATOR
    . './Zend' . PATH_SEPARATOR
    . get_include_path()
);

/**
 * Load configuration
 */
require 'Zend/Config/Ini.php';
$Config = new Zend_Config_Ini('./config/config.ini', 'general');

/**
 * Setup the Registry
 */
require 'Zend/Registry.php';
$Registry = Zend_Registry::getInstance();
$Registry->set('Configuration', $Config);

/**
 * Environmental Settings
 */
ini_set('display_errors', $Config->bootstrap->displayerrors);
error_reporting((int) $Config->bootstrap->reporting);
date_default_timezone_set($Config->bootstrap->timezone);

/**
 * Detect Base URL from sanitised PHP_SELF Server variable.
 * Alternatively, can of course make this a one-shot config variable.
 */
$_SERVER['PHP_SELF'] = substr($_SERVER['PHP_SELF'], 0, strlen($_SERVER['PHP_SELF']) - @strlen($_SERVER['PATH_INFO']));
$baseUrl = substr($_SERVER['PHP_SELF'], 0, -9); // strips the ending "index.php"

/**
 * Get a Front Controller instance
 */
require 'Zend/Controller/Front.php';
$Front = Zend_Controller_Front::getInstance();

/**
 * Setup the Front Controller with any application specific settings.
 * Add a list of all Modularised controller directories (incl. default)
 */
$Front->throwExceptions((bool) $Config->bootstrap->renderexceptions);
$Front->setBaseUrl($baseUrl);
$Front->setControllerDirectory(array(
    'default'=>'./default/controllers'
));
$Front->setParam('Registry', $Registry);
$Front->returnResponse(true);

/**
 * Dispatch the Request and send the Response
 */
$Response = $Front->dispatch();
$Response->sendResponse();
Removed the fluent interface - one of the previous comments about methods being unrelated is quite true.

Just to round out the $Front->setParam() usage...
Example Zend_Controller_Action base class (./extends/zfPetShop/Controller/Action.php):

Code: Select all

<?php

class zfPetShop_Controller_Action extends Zend_Controller_Action
{

    /**
     * Instance of Zend_Registry passed by Zend_Controller_Front as an
     * invocation argument.
     *
     * @var Zend_Registry
     * @access protected
     */
    protected $registry = null;

    /**
     * init() method called by Zend_Controller_Action constructor to setup
     * this Action (or its subclasses). Used here to assign FC params as
     * class properties.
     *
     * @return void
     * @access public
     */
    public function init()
    {
        if ($this->getInvokeArg('Registry') instanceof Zend_Registry) {
            $this->registry = $this->getInvokeArg('Registry');
        } 
    }

}
Like any Zend_Controller_Action subclass - should be extended by all application specific controllers to gain access to the Registry member it adds. Since the subclass might be re-useable in other applications a better prefix than "zfPetShop" is probably a good idea though.
Last edited by Maugrim_The_Reaper on Thu Apr 12, 2007 2:55 pm, edited 1 time in total.
User avatar
bpopp
Forum Newbie
Posts: 15
Joined: Mon Apr 09, 2007 11:18 pm
Location: Memphis

Post by bpopp »

Sorry if you've already explained this, but where would external libraries (like Smarty) go? Are you just assuming they won't be used in this demo?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Maugrim_The_Reaper wrote:Another overall stab - using the recent filesystem layout, and my (possible) config and index/bootstrap shell.
I hate to admit it, but overall I really like the set of tradeoffs you have made to keeps things similar to the Zend layouts but to move them forward a step. :) My two qualms about application.php is that it puts code in a different than expected place and adds another include, but I like the concept of having all the code in the application directory.

This configuration also implies that you always use modules, which may be the right thing to do in the long run.

I saw you email in the ZF mailing list about this. I think it cuts both ways. You may want share the bootstrap, but you also might want to have custom bootstraps for each.

In answer to bpopp they would simply go into the 'application' directory so everything is can be included using that single path (note where Zend directory is). On that note, should there be a "zfPetShop" directory that would contain "zfPetShop/Controller/Action.php" and would that me better as "Zfpetshop/Controller/Action.php" or "Zps/Controller/Action.php"
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Sorry if you've already explained this, but where would external libraries (like Smarty) go? Are you just assuming they won't be used in this demo?
The sample application is intended to initially rely totally on the framework - there are no other libraries. Of course, knowing the framework some alternate libraries in a real application (and even this one since we're assessing for the "best" objective option) are likely. For example, I don't use Zend_Db (prefer an ORM), or Zend_Mail (prefer Swiftmailer) in my own apps (here I doubt my personal favs carry weight - my "ORM" solution is very much a simpleton setup anyhow). These would go in the main application directory, same level as /Zend. Depending on Pathing Convention, they may require additional include_paths of course.

If that's not preferable, or the number starts diluting the main directories, just add a /library subdirectory to hold them. The only difference is an extra /library bit added to the include_paths. Having a /library is in the ZF recommendations but its premature until there's a real need for it. I suppose arborints minimal preference rubbed off a little ;).
I hate to admit it, but overall I really like the set of tradeoffs you have made to keeps things similar to the Zend layouts but to move them forward a step. Smile My two qualms about application.php is that it puts code in a different than expected place and adds another include, but I like the concept of having all the code in the application directory.
I feel it's good to stay similar to the ZF recommendations - no need though to be identical though. They already have changed that recommendation themselves a few times. On the bootstrap - module paths should be configurable I think. It does put code where it's not expected - but it effectively eliminates the duplication the mailing list poster was having issues with, and which softlinking can't cleanly resolve. It also helps clean up pathing - no need for an absolute path to be configured in the bootstrap (just the index.php shell).

Either option is workable - just felt this fleeting inspiration looked a bit prettier...;). Also I spotted two typos I made in the code - I should really test this stuff quickly before posting it. Fixed them above.
This configuration also implies that you always use modules, which may be the right thing to do in the long run.
It seems the recent "in" thing to do. It does make some sense if an application has several distinct modules. For example, the Java Pet Store has the store component, an RSS feed, and a few other separable areas. It could turn out it's overkill - but having the default setup at least leaves the option open.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

A side-effect of using bootstrap.php is that we can locate the application dir via the bootstrap file. Here is a simplified example:

Code: Select all

<?php

// we locate the application dir via this bootstrap file
$AppDir = dirname(__FILE__);

/**
 * Include path setup prepend application dir to paths defined in php.ini
 * Assume that Zend and Smarty will be in the root application dir
 */
set_include_path($AppDir . PATH_SEPARATOR . get_include_path());

/**
 * Load configuration
 */
require 'Zend/Config/Ini.php';
$Config = new Zend_Config_Ini($AppDir . '/config/config.ini', 'general');

/**
 * Setup the Registry
 */
require 'Zend/Registry.php';
$Registry = Zend_Registry::getInstance();
$Registry->set('Configuration', $Config);

/**
 * Environmental Settings
 */
ini_set('display_errors', $Config->bootstrap->displayerrors);
error_reporting((int) $Config->bootstrap->reporting);
date_default_timezone_set($Config->bootstrap->timezone);

$baseUrl = ;

/**
 * Get a Front Controller instance
 */
require 'Zend/Controller/Front.php';
$Front = Zend_Controller_Front::getInstance();

/**
 * Setup the Front Controller with any application specific settings.
 * Add a list of all Modularised controller directories (incl. default)
 */
$Front->throwExceptions((bool) $Config->bootstrap->renderexceptions);
$Front->setBaseUrl($Config->baseURL);
$Front->setControllerDirectory(array(
    'default'=>$AppDir . '/default/controllers'
));
$Front->setParam('Registry', $Registry);
$Front->returnResponse(true);

/**
 * Dispatch the Request and send the Response
 */
$Response = $Front->dispatch();
$Response->sendResponse();
(#10850)
User avatar
bpopp
Forum Newbie
Posts: 15
Joined: Mon Apr 09, 2007 11:18 pm
Location: Memphis

Post by bpopp »

In answer to bpopp they would simply go into the 'application' directory so everything is can be included using that single path (note where Zend directory is). On that note, should there be a "zfPetShop" directory that would contain "zfPetShop/Controller/Action.php" and would that me better as "Zfpetshop/Controller/Action.php" or "Zps/Controller/Action.php"
I'm not sure that scales. I like to piggy-back off the sweat and tears of others and am often including libraries from various projects whenever I can. In my quick little prototype I already have 4 different packages and it's not doing much of anything. Why not just have a library folder at the top level and throw everything in there? I'm using something like this:

Code: Select all

config
application
library
  Smarty
  Zend
  dBug
  MyZend  <-- Where I put my Zend Customizations
public
I use an .htaccess file to change my include_path, but you wouldn't necessarily have to. It's not too much more typing to append a library/ in front of your includes and it makes it much easier, in my mind, to see what's part of the framework and what's been brought in externally.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I don't know if it "scales" any different either way. The chance of name clashes is between low and non-existant. And, if you want to have a library dir on the same level as the application dir that is a fine alternative -- just add both to your search path. Either way you load them in the code with simply "Lib/Dir/File.php".

My point of this is that 95%+ cases you will have no more than a couple of other libraries besides Zend and probably 80%+ only Zend. The reason I suggested this is because of the inverse -- having a "library" directory does not scale down very well.

And finally, putting everything in a single directory makes packaging/deploying the application really easy and clean.
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

I'm not sure that scales.
Depends entirely on the number of directories involved. I typically use ZF, Swiftmailer, Quantum_Db in my smaller apps. That number isn't large enough to really need a /library. It's worth a little note that given the structure of multiple libraries the suggested one above could place base-files (e.g. Swift.php from Swiftmailer) into the base application directory. While this is not all that pretty (and involves a little manual wrangling outside a subversion external) it cuts down on the number of include_paths PHP needs to search.

I might actually ask about that base Swiftmailer class - if it was shifted into the directory structure by just one step it would clean up my subversion externals very nicely...
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

A while back I opened a Wiki category over on PFP: http://www.patternsforphp.com/wiki/ZF_Petshop_Demo
My aim with it was to be part ideas box, part tutorial. I'm giving a little more thought to making it a more formal implementation guide. I know we have the ZF manual, and tutorials, but there's not much out there binding together an application on a slightly larger scale. As we move along on this topic I'll continue extending the Wiki at a bit more length - I think it would make an interesting accompanying read to the application code itself. Any thoughts or suggestions are welcome.

Back on topic - willing to adopt arborint's changes in the last code examples. Are we at a stage where Subversion has a filesystem/bootstrap worth committing initially?
User avatar
bpopp
Forum Newbie
Posts: 15
Joined: Mon Apr 09, 2007 11:18 pm
Location: Memphis

Post by bpopp »

I don't know if it "scales" any different either way. The chance of name clashes is between low and non-existant. And, if you want to have a library dir on the same level as the application dir that is a fine alternative -- just add both to your search path. Either way you load them in the code with simply "Lib/Dir/File.php".
I'm not so much worried about name clashes. My concern is with people installing "our" app with little to no knowledge of ZF. If everything is thrown into the same directory, it's hard to know what is a part of the base framework, what has been added by our programmers, and what has been included as libraries from other projects. Anything we can do to help with this is a good thing. You and I may know that "Smarty" and "ADODB" are PHP libraries, but someone new to PHP isn't going to know that. They could inadvertantly go in there and start mucking around with it, not knowing that they've just unknowingly forked a major project.

Maug, I would have expected you to agree with me since Astrum has it's own "library" folder for exactly this purpose. I see a lot of benefits and no cons? Help me out.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

bpopp wrote:I'm not so much worried about name clashes. My concern is with people installing "our" app with little to no knowledge of ZF. If everything is thrown into the same directory, it's hard to know what is a part of the base framework, what has been added by our programmers, and what has been included as libraries from other projects. Anything we can do to help with this is a good thing. You and I may know that "Smarty" and "ADODB" are PHP libraries, but someone new to PHP isn't going to know that. They could inadvertantly go in there and start mucking around with it, not knowing that they've just unknowingly forked a major project.
I honestly don't think that someone who can't tell the difference between the Zend and Smarty directories and the config/controllers/models/views directories is on our radar at this point. And this is really more about setting an convention for include statements. You can always add more paths to the include_path and separate the directories -- this is just the simplest base configuration.
bpopp wrote:Maug, I would have expected you to agree with me since Astrum has it's own "library" folder for exactly this purpose. I see a lot of benefits and no cons? Help me out.
I see pros and cons for both sides:

Separate library directory:
- clear separation of custom code and packaged libraries
- ability to customize placement of library directory

All-in-one directory:
- simplest packaging and deployment for the application
- single search path in include_path
- many installations will have Zend and Smarty already in a global directory (e.g. "/usr/local/lib/php")

And this is not a final decision, we just wrangled this thing down for this round to move things along.
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Should be noted nobody is saying a /library dir is bad - it's just not required as yet with only the one library in use. :)

Back on topic. Any thoughts on whether __autoload (or an SPL registered function in case it conflicts elsewhere) function should be used or not? Benefits are not needing the persistent "require_once" calls anymore. A con is that autoloaded classes lose a small performance gain under an opcode cache. Far as I know the loss is tiny, and I'll note this topic isn't well described on the web (though a ZF mailing list discussion seemed to sort out the opcode issue was pretty miniscule since the file is indeed cached).

On the extended class naming - I think "Zps" as a namespace works. It's generic enough. Also need to add /extends to the include_path. We could just remove the "extends" dir and have the /Zps dir at the same level as /Zend? It's a bit of a special case hence the original suggestion for /extends...

Like all good applications, I'm going to just add a default Index to echo "Hello, World!". Hopefully have subversion access soon to import the code - I have most of it added to the relevant files in the suggested filesystem.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Maugrim_The_Reaper wrote:Should be noted nobody is saying a /library dir is bad - it's just not required as yet with only the one library in use. :)
I agree. We may or may not end up there ... or it may be an option.
Maugrim_The_Reaper wrote:Back on topic. Any thoughts on whether __autoload (or an SPL registered function in case it conflicts elsewhere) function should be used or not? Benefits are not needing the persistent "require_once" calls anymore. A con is that autoloaded classes lose a small performance gain under an opcode cache. Far as I know the loss is tiny, and I'll note this topic isn't well described on the web (though a ZF mailing list discussion seemed to sort out the opcode issue was pretty miniscule since the file is indeed cached).
I say we try it without autoload and down the road see if we are doing anything that would prevent it use. That we would not want to do. It is a programmer preference and it should be an option. We may find a reason to prefer autoload though, so we should remain open to it.
Maugrim_The_Reaper wrote:On the extended class naming - I think "Zps" as a namespace works. It's generic enough. Also need to add /extends to the include_path. We could just remove the "extends" dir and have the /Zps dir at the same level as /Zend? It's a bit of a special case hence the original suggestion for /extends...
The naming decisions are the hardest ;) so if you like that one, I like that one!
Maugrim_The_Reaper wrote:Like all good applications, I'm going to just add a default Index to echo "Hello, World!". Hopefully have subversion access soon to import the code - I have most of it added to the relevant files in the suggested filesystem.
Excellent.

I propose that we punt any discussion about the front controller, router, dispatcher down the road and move on the the Action Controller code next, starting with the code you posted.
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

Small changes to the bootstrap:

Code: Select all

<?php

$AppDir = dirname(__FILE__);

/**
 * Include path setup (assuming no facility to edit php.ini)
 * Also assumed only Core ZF. Includes the Zps parent directory.
 * Left out Models for now.
 */
set_include_path(
    $AppDir . PATH_SEPARATOR
    . $AppDir . '/extends' . PATH_SEPARATOR
    . get_include_path()
);

/**
 * Load configuration
 */
require 'Zend/Config/Ini.php';
$Config = new Zend_Config_Ini($AppDir . '/config/config.ini', 'general');

/**
 * Setup the Registry
 */
require 'Zend/Registry.php';
$Registry = Zend_Registry::getInstance();
$Registry->set('Configuration', $Config);

/**
 * Environmental Settings
 */
ini_set('display_errors', $Config->bootstrap->displayerrors);
error_reporting((int) $Config->bootstrap->reporting);
date_default_timezone_set($Config->bootstrap->timezone);

/**
 * Get a Front Controller instance
 */
require 'Zend/Controller/Front.php';
$Front = Zend_Controller_Front::getInstance();

/**
 * Setup the Front Controller with any application specific settings.
 * Add a list of all Modularised controller directories (incl. default)
 */
$Front->throwExceptions((bool) $Config->bootstrap->renderexceptions);
if (!empty($Config->bootstrap->baseurl)) {
    $Front->setBaseUrl($Config->bootstrap->baseurl);
}
$Front->setControllerDirectory(array(
    'default'=> $AppDir . '/default/controllers'
));
$Front->setParam('Registry', $Registry);
$Front->returnResponse(true);

/**
 * Dispatch the Request and send the Response
 */
$Response = $Front->dispatch();
$Response->sendResponse();
Base URL now moved to the config file. Using the $AppDir variable in place of the relatives.

The basic Index Controller would be located at /default/controllers/IndexController.php and extends the previously posted Zend_Controller_Action subclass.

Code: Select all

<?php

/** Zps_Controller_Action */
require_once 'Zps/Controller/Action.php';

class IndexController extends Zps_Controller_Action
{
 
    public function indexAction()
    {
        $this->getResponse()->setBody('Hello World!');
    }

}
One untidy bit is that non-existent controller/action paths in the url will result in uncaught exceptions which are better handled as 404s since they relate to non-existent resource.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Looks good. My only quibble is that baseurl will probably be used frequently enough through the app that I think just $Config->baseurl would be better.

I quickly put together the directory structure and code and ... nothing output! :) I'll have to go back an take a look when I get time.
(#10850)
Post Reply