2
votes

i got a problem regarding Unit-testing a Zend-Framework application under Ubuntu 12.04. The project-structure is a default zend application whereas the models are defined as the following

./application
  ./models
    ./DbTable
      ./ProjectStatus.php (Application_Model_DbTable_ProjectStatus)
    ./Mappers
      ./ProjectStatus.php (Application_Model_Mapper_ProjectStatus)
    ./ProjectStatus.php   (Application_Model_ProjectStatus)

The Problem here is with the Zend-specific autoloading. The naming convention here appears that the folder Mappers loads all classes with _Mapper but not _Mappers. This is some internal Zend behavior which is fine so far.

On my windows machine the phpunit runs without any Problems, trying to initiate all those classes.

On my Ubuntu machine however with jenkins running on it, phpunit fails to find the appropriate classes giving me the following error

Fatal error: Class 'Application_Model_Mapper_ProjectStatus' not found 
in /var/lib/jenkins/jobs/PAM/workspace/tests/application/models/Mapper/ProjectStatusTest.php
on line 39

The error appears to really be that the Zend-Autoloader doesn't load from the ubuntu machine, but i can't figure out how or why this works. The question remains of why this is. I think i've double checked every point of contact with the zend autoloading stuff, but i just can't figure this out. I'll paste the - from my point of view relevant snippets - and hope someone of you has any insight to this.

Jenkins Snippet for PHPUnit

 <target name="phpunit" description="Run unit tests with PHPUnit">
   <exec executable="phpunit" failonerror="true">
     <arg line="--configuration '${basedir}/tests/phpunit.xml' --coverage-clover '${basedir}/build/logs/clover.xml' --coverage-html '${basedir}/build/coverage/.' --log-junit '${basedir}/build/logs/junit.xml'" />
   </exec>
 </target>

./tests/phpunit.xml

<phpunit bootstrap="./bootstrap.php">
   ... this shouldn't be of relevance ...
</phpunit>

./tests/bootstrap.php

<?php
// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'testing'));

// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Zend/Loader/Autoloader.php';    
Zend_Loader_Autoloader::getInstance();

Any help will be appreciated.

3

3 Answers

2
votes

I actually think the problem is with your "models/Mappers" folder. It should be "models/mappers" (all lowercase), and would explain why it works on Windows and not Linux.

As you can see from the Zend_Application_Module_Autoloader class:

$this->addResourceTypes(array(
            'dbtable' => array(
                'namespace' => 'Model_DbTable',
                'path'      => 'models/DbTable',
            ),
            'mappers' => array(
                'namespace' => 'Model_Mapper',
                'path'      => 'models/mappers',
            ),

However, as per my previous answer, I still believe you will need to bootstrap the application for all the default resources to be added automatically

1
votes

From the ZF Manual:

Create a Model and Database Table

Before we get started, let's consider something: where will these classes live, and how will we find them? The default project we created instantiates an autoloader. We can attach other autoloaders to it so that it knows where to find different classes. Typically, we want our various MVC classes grouped under the same tree -- in this case, application/ -- and most often using a common prefix.

Zend_Controller_Front has a notion of "modules", which are individual mini-applications. Modules mimic the directory structure that the zf tool sets up under application/, and all classes inside them are assumed to begin with a common prefix, the module name. application/ is itself a module -- the "default" or "application" module. As such, we'll want to setup autoloading for resources within this directory.

Zend_Application_Module_Autoloader provides the functionality needed to map the various resources under a module to the appropriate directories, and provides a standard naming mechanism as well. An instance of the class is created by default during initialization of the bootstrap object; your application bootstrap will by default use the module prefix "Application". As such, our models, forms, and table classes will all begin with the class prefix "Application_".

Since Zend_Application_Module_Autoloader is loaded by default, you should only need to bootstrap your application (you don't have to run the front controller) as per the example at the of this answer.

If you don't want to bootstrap your application, you could short circuit the resource loading by initialising Zend_Application_Module_Autoloader yourself:

$autoloader = new Zend_Application_Module_Autoloader();

As you can see from the code, the __construct of this class calls initDefaultResourceTypes(), with all the goodies you are looking for:

class Zend_Application_Module_Autoloader extends Zend_Loader_Autoloader_Resource
{
    /**
     * Constructor
     *
     * @param  array|Zend_Config $options
     * @return void
     */
    public function __construct($options)
    {
        parent::__construct($options);
        $this->initDefaultResourceTypes();
    }

    /**
     * Initialize default resource types for module resource classes
     *
     * @return void
     */
    public function initDefaultResourceTypes()
    {
        $basePath = $this->getBasePath();
        $this->addResourceTypes(array(
            'dbtable' => array(
                'namespace' => 'Model_DbTable',
                'path'      => 'models/DbTable',
            ),
            'mappers' => array(
                'namespace' => 'Model_Mapper',
                'path'      => 'models/mappers',
            ),
            'form'    => array(
                'namespace' => 'Form',
                'path'      => 'forms',
            ),
            'model'   => array(
                'namespace' => 'Model',
                'path'      => 'models',
            ),
            'plugin'  => array(
                'namespace' => 'Plugin',
                'path'      => 'plugins',
            ),
            'service' => array(
                'namespace' => 'Service',
                'path'      => 'services',
            ),
            'viewhelper' => array(
                'namespace' => 'View_Helper',
                'path'      => 'views/helpers',
            ),
            'viewfilter' => array(
                'namespace' => 'View_Filter',
                'path'      => 'views/filters',
            ),
        ));
        $this->setDefaultResourceType('model');
    }
}

To only bootstrap your application without running the front controller in tests/bootstrap.php:

<?php
// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'testing'));

// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Zend/Loader/Autoloader.php';  

$config = array(
    APPLICATION_PATH . '/configs/application.ini'
);

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    array('config' => $config)
);

$application->bootstrap();
0
votes

Since the conventional Autoloader did not work, i tried to manually do what any Zend application would do. Here's the bootstrap.php that worked out in the end

<?php    
// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'testing'));

// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Zend/Loader/Autoloader.php';
require_once 'Zend/Loader/Autoloader/Resource.php';
$resources = new Zend_Loader_Autoloader_Resource(array(
  'namespace' => 'Application',
  'basePath'  => APPLICATION_PATH
));

$resources->addResourceType('form','forms','Form');
$resources->addResourceType('model','models','Model');
$resources->addResourceType('dbtable','models/DbTable','Model_DbTable');
$resources->addResourceType('mapper','models/Mappers','Model_Mapper');

The logic usually is what Zend should figure out on his own. And in fact it does so on my local development machine running on windows. On ubuntu however i need to become specific.

Would be interesting to know why that is. If someone can explain the reasoning to me, then I'll probably end up giving you the right answer ;)