1

I'm using the zend framework to write an app and I'm having lots of problems getting my relational database models to work. I've read the quickstart and the docs several times and I'm still not sure how to do this. I post a picture of the relationships between the different tables involved to avoid having to explain everything since English is not my first language and I tend not to make myself clear when I try to explain...well complex things. enter image description here

The tables press_releases, social_networking, blog_posts, rss_feed, directorios, users and articulos are all set as foreign keys in the table named planilla_users. I coded my table models the way the quickstart shows:

class Application_Model_DbTable_PlanillaUsers extends Zend_Db_Table_Abstract
{

protected $_name = 'planilla_users';
protected $_referenceMap = array(
    'User' => array(
        'columns' => 'users_id',
        'refTableClass' => 'Application_Model_DbTable_Users',
        'refColumns' => 'id'
    ),
    'Articulo' => array(
        'columns' => 'art_id',
        'refTableClass' => 'Application_Model_DbTable_Articulos',
        'refColumns' => 'id'
    ),...etc

...and the rest following the format of:

class Application_Model_DbTable_Users extends Zend_Db_Table_Abstract
{

protected $_name = 'users';
protected $_dependentTables = array('Application_Model_DbTable_PlanillaUsers'); 

I also have a model Planilla.php with all the setters and getters for the information to be stored/updated/retrieved/deleted,...however...I'm completely blank on what to do with the mapper. I don't know how it's supposed to work and I honestly haven't found a good example on how to do something like this yet. So any help would be mostly appreciated.

la_f0ka
  • 1,773
  • 3
  • 23
  • 44
  • Have you seen this chapter: http://framework.zend.com/manual/en/zend.db.table.relationships.html ? It is from the reference guide and not from the quick start guide and it should give you all your answers. – Raffael Luthiger May 30 '11 at 21:51
  • @Raffael Luthiger Yes, I've seen it. However I don't know how am I supposed to get the mapper to work when I have 8 different tables involved in a many-to-one relationship. How do I fetchAll() for example? – la_f0ka May 30 '11 at 21:59
  • How does this relate to your [previous question](http://stackoverflow.com/questions/6177710/how-to-set-a-mapper-for-a-table-that-will-serve-as-a-record-for-other-7-table-ids)? Maybe delete that question if this one replaces it? – David Weinraub May 31 '11 at 05:08
  • Perhaps take a look at [How to easily create Models and Table Relationships in Zend Framework](http://mattmccormick.ca/2010/04/24/how-to-easily-create-models-and-table-relationships-in-zend-framework/). Much of it is similar to what you already have defined, but it demonstrates in some measure how to actually use defined relationships. – David Weinraub May 31 '11 at 06:42
  • @David Weinraub, thanks a lot for the article, I'll check it out now. And I deleted the other question, so thanks for making me notice – la_f0ka May 31 '11 at 12:29

1 Answers1

3

My advice to you would be to have a look at Doctrine, it is an object relational mapper which can intergrate quite nicely with ZF. I personally use version 1.2 with ZF 1.11.4 with no problems.

There is a nice screen cast here which explains how to intergate Doctrine into ZF. Doctrine can be a bit of a pig to to learn but once you understand it, it will save you time in the long run. Doctrine also comes with a command line script which can be used to reverse engineer databases into Doctrine classes. If you have the relations set on the database then Doctrine can pick that up too.

I also hear that ZF 2 will have Doctrine 2.0 included.

The method I use to create the Doctrine classes is to first setup doctrine in your appication.ini file, add these lines somewhere under production header.

[production]

//...

; Doctrine settings
pluginpaths.Freedom_Zend_Application_Resource = "Freedom/Zend/Application/Resource"
resources.doctrine.connection_string = "mysql://username:password@localhost/database_name"
resources.doctrine.compiled = false ; use compiled version of Doctrine
resources.doctrine.cache = false ; use query cache

; Information required for models generator
resources.doctrine.models_path = APPLICATION_PATH "/modules/default/models/Doctrine"
resources.doctrine.module_directories[] = APPLICATION_PATH "/modules/default/models/Doctrine/base"  
resources.doctrine.module_directories[] = APPLICATION_PATH "/modules/default/models/Doctrine"

; Generator settings
resources.doctrine.generate_models_options.phpDocPackage = Your App Name
resources.doctrine.generate_models_options.phpDocSubpackage = Doctrine
resources.doctrine.generate_models_options.phpDocName = Your Company Name
resources.doctrine.generate_models_options.phpDocEmail = your@email.address
resources.doctrine.generate_models_options.pearStyle = true
resources.doctrine.generate_models_options.generateTableClasses = true
resources.doctrine.generate_models_options.generateBaseClasses = true
resources.doctrine.generate_models_options.classPrefix = "Model_Doctrine_"
resources.doctrine.generate_models_options.baseClassPrefix = "Base_"
resources.doctrine.generate_models_options.baseClassesDirectory =
resources.doctrine.generate_models_options.classPrefixFiles = false
resources.doctrine.generate_models_options.generateAccessors = false
//...

You will notice a line at the top pluginpaths.Freedom_Zend_Application_Resource Freedom is my generic namespace in my library (see folder tree below). I here I have a Zend folder where I can place my extra ZF code, this includes overiding existing ZF functions if required. Freedom is my company name, yours will obviously differ. This line will need to change to your company's name, for example pluginpaths.Yourcompany_Zend_Application_Resource = "Yourcompany/Zend/Application/Resource"

The next line is where you place your database connection settings resources.doctrine.connection_string = "mysql://username:password@localhost/database_name"

The next three lines:

resources.doctrine.models_path = APPLICATION_PATH "/modules/default/models/Doctrine"
resources.doctrine.module_directories[] = APPLICATION_PATH "/modules/default/models/Doctrine/Base"  
resources.doctrine.module_directories[] = APPLICATION_PATH "/modules/default/models/Doctrine"

tell Doctrine where to place the classes generated, as I am using a modular setup these go into my application/modules/default/models/Doctrine and application/modules/default/models/Doctrine/Base respectively (see folder tree below).

There are some other lines that will need changing, these should be self evident.

You will also need to add the following lines under the development heading in your application.ini.

[development : production]

//...

; phpSettings
resources.doctrine.compiled = false ; use compiled version of Doctrine
resources.doctrine.cache = false ; use query cache

//...

You will also need the resource file Doctrine.php. Where this is located depends on your setup, mine is as follows.

-Application
  -configs
  -layouts
  -modules
    -default // ZF default controller
      -controllers
      -models
        -Doctrine // folder for you generated classes
          -Base // Do not change the files in this folder
    -views
      -scripts
-Library
  -Doctrine // the doctrine application as downloaded
   doctrine-cli.php // you will need to create this (see below)
   Doctrine.php // come with the doctrine application
  -Freedom // my generic namespace, shared across multiple apps
    -Zend // place to overide/add ZF classes
      -Application
        -Resource
          *Docrine.php // the file below
  -Zend // ZF 1.11.4 (yours may differ)
  -ZendX // ZF extras

The *Doctrine.php file is as follows (obviously ignore the *!!)

/**
* Doctrine application resource
*
* @author Juozas Kaziukenas (juozas@juokaz.com)
*/
class Freedom_Zend_Application_Resource_Doctrine extends Zend_Application_Resource_ResourceAbstract
{
    /**
* Initialize
*/
    public function init()
    {
        $doctrineConfig = $this->getOptions();

        if (isset($doctrineConfig['compiled']) && $doctrineConfig['compiled'] == true &&
            file_exists(APPLICATION_PATH . '/../library/Doctrine.compiled.php'))
        {
            require_once 'Doctrine.compiled.php';
        }
        else
        {
            require_once 'Doctrine.php';
        }
        $loader = Zend_Loader_Autoloader::getInstance();
        $loader->pushAutoloader(array('Doctrine_Core', 'autoload'), 'Doctrine');

        $manager = Doctrine_Manager::getInstance();

        // set models to be autoloaded and not included (Doctrine_Core::MODEL_LOADING_AGGRESSIVE)
        $manager->setAttribute(
            Doctrine_Core::ATTR_MODEL_LOADING,
            Doctrine_Core::MODEL_LOADING_CONSERVATIVE
        );

        // enable ModelTable classes to be loaded automatically
        $manager->setAttribute(
            Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES,
            true
        );

        // enable validation on save()
        $manager->setAttribute(
            Doctrine_Core::ATTR_VALIDATE,
            Doctrine_Core::VALIDATE_ALL
        );

        // enable accessor override
        $manager->setAttribute(
            Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE,
            true
        );

        // enable sql callbacks to make SoftDelete and other behaviours work transparently
        $manager->setAttribute(
            Doctrine_Core::ATTR_USE_DQL_CALLBACKS,
            true
        );

        // enable automatic queries resource freeing
        $manager->setAttribute(
            Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS,
            true
        );

        // connect to database
        $manager->openConnection($doctrineConfig['connection_string']);

        // set to utf8
        $manager->connection()->setCharset('utf8');

        if (isset($doctrineConfig['cache']) && $doctrineConfig['cache'] == true)
        {
            $cacheDriver = new Doctrine_Cache_Apc();

            $manager->setAttribute(
                Doctrine_Core::ATTR_QUERY_CACHE,
                $cacheDriver
            );
        }

        return $manager;
    }
}

Next you will need to create the doctrine-cli.php file to bootstrap your application this should be located in the root of your library file (see tree above), mine is as follows.

/**
 * Doctrine CLI script
 *
 * @author Juozas Kaziukenas (juozas@juokaz.com)
 */

define('APPLICATION_ENV', 'development');
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../../your/application'));

set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    './',
    get_include_path(),
)));

require_once 'Zend/Application.php';

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

$application->getBootstrap()
        ->bootstrap('doctrine')
        ->bootstrap('autoload');

// set aggressive loading to make sure migrations are working
Doctrine_Manager::getInstance()->setAttribute(
    Doctrine::ATTR_MODEL_LOADING,
    Doctrine_Core::MODEL_LOADING_AGGRESSIVE
);

$options = $application->getBootstrap()->getOptions();

$cli = new Doctrine_Cli($options['resources']['doctrine']);

$cli->run($_SERVER['argv']);

The only line you should need to change is

define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../../your/application'));

so it points to your application's root folder, similar to the line in your index.php file in your public folder.

Now hopefully you should be ready to generate your database class files. Goto a terminal window and entrer the following.

cd /home/path/to/library
php doctrine-cli.php generate-models-db

If all has gone well the folders application/modules/default/models/Doctrine and application/modules/default/models/Doctrine/Base should contain the classes for your database. As I mentioned above do not change the files in the Base folder, you can use the classes in the parent Doctrine folder to make changes. You will also notice that there are two classes per database table in the Doctrine folder, one is suffixed with Table. Here I tend to place my DQL code to keep it away from my models/controllers. The other classes can be used to make changes to the classes in the base folder, also you can add hooks and listeners here to add table specific code such as adding password encryption or presetting dates etc.

I hope I have explained it clearly enough as it is a pig of a job to get working but this is how mine is set up.

I hope this helps.

Garry
  • 1,455
  • 1
  • 15
  • 34
  • Well @Garry, thanks for that answer. I have heard nothing but nice things about Doctrine but since I'm currently just approaching to the Zend Framework itself I'm afraid picking Doctrine might be a little to harsh for me...at least at this stage. I will however check the screen cast before making up my mind about this. It might be not be as hard as I imagine right now. – la_f0ka May 31 '11 at 12:34
  • I am following the zend cast tutorial on ZF/Doctrine integration right now. And I'm also looking for info on how to generate the classes for my exsisting db tables...(since I think that doing it manually might be a little bit...error prone.) I don't seem to find anything...have you done or read about how to accomplish this? – la_f0ka May 31 '11 at 15:53
  • @la_f0ka Sorry for the delay in getting back to you. I have modified the above to hopefully get you going with Doctrine. Some of this is from memory so I hope I have not missed anything. Please let me know how you get on – Garry Jun 01 '11 at 14:16
  • @Garry thanks a lot for your help. I had (kind of) already got the models to work with 'orm:convert-mapping --from-database yml ~/' and 'orm:generate-entities ~/', however these models are lacking a lot of information (they don't even have the namespaces set). I did however follow your instructions because it seemed a hell of a lot easier than what I had done. on 'generate-models-db' execution I got this error though: – la_f0ka Jun 01 '11 at 14:58
  • 'PHP Warning: call_user_func() expects parameter 1 to be a valid callback, class 'Doctrine_Core' not found in /home/fiodorovich/library/ZendFramework/library/Zend/Loader/Autoloader.php on line 124', & 'PHP Fatal error: Class 'Doctrine_Manager' not found in /home/fiodorovich/public_html/gisele/library/Federico/Zend/Application/Resource/Doctrine.php on line 28'... Any ideas on what could be causing this? – la_f0ka Jun 01 '11 at 14:59
  • @la_f0ka Have you got a Doctrine namespace setup? In your application.ini autoloadernamespaces.doctrine = "Doctrine_" – Garry Jun 01 '11 at 16:20
  • @Garry Yes it was actually loaded as "Doctrine" without the underscore. I just added your version of it and I actually got a couple of extra error messages. 'Warning: include_once(Doctrine/Core.php) [function.include-once]: failed to open stream: No such file or directory in /home/fiodorovich/library/ZendFramework/library/Zend/Loader.php on line 146'. I don't know if its related but I cannot find the doctrine.compile.php file – la_f0ka Jun 01 '11 at 16:34
  • @la_f0ka I have never come across a doctrine.compile.php file before. Is Doctrine installed in the same folder as Zend? I looks like ZF autoloader cannot find it. The Doctrine folder must be in your include path as set in index.php and in doctrine-cli.php. – Garry Jun 01 '11 at 16:44
  • @Garry, the Doctrine folder is inside library which itself is set in index.php loaded: 'set_include_path(implode(PATH_SEPARATOR, array( realpath(APPLICATION_PATH . '/../library'), get_include_path(), )));' ... Zend however is not there...it's in an external library: '/home/fiodorovich/library/ZendFramework/library/Zend' – la_f0ka Jun 01 '11 at 16:52
  • @la_f0ka Hmmm, this is strange as to why ZF cannot load the Doctrine/Core.php file. I assume that the Doctrine folder does have a capital 'D' and contains the file Core.php. – Garry Jun 01 '11 at 17:11
  • @Garry actually the Doctrine folder has the folders Common, DBAL and ORM. I'm using Doctrine 2 ...and I actually also have doctrine installed with PEAR...which might somehow be messing with this. – la_f0ka Jun 01 '11 at 17:15
  • @la_f0ka That could be it, I am using Doctrine 1.2.4. As I understand it Doctrine 2 is a different kettle of fish. I don't know how to intergrate Doctrine 2 into ZF but I have heard that it can be done. I don't plan to move to Docrine 2 until ZF2 comes out. You could install 1.2.4 and try again, that maybe the easiet soloution for you. [Doctrine Download](http://www.doctrine-project.org/projects/orm/download) – Garry Jun 01 '11 at 17:22
  • @Garry yup, that was it. Downloaded the good ol' Doctrine and it all worked perfectly. Thanks a lot for your help Garry. Might I ask you what's the difference between the models in modules/default/models/Doctrine and the ones in modules/default/models/Doctrine/Base ?? just to know what to look for next and finally start working with Doctrine/ZF?? – la_f0ka Jun 01 '11 at 17:31
  • The Base folders files will be overwritten everytime you run `php doctrine-seevee-cli.php generate-models-db` so any changes you make will be overwritten. The files in the parent folder are only written the first time you run it. The files with the same name in the Doctrine folder can be used to override settings in the Base folders files. For example you can change, add or remove relationships in these files easily. – Garry Jun 01 '11 at 17:43
  • ok @Garry, thanks a lot for taking the time to explain all of this to me. I know I can be hard-skulled sometimes, so I appreciate your help very much. You've been far more helpful than anyone in #doctrine and #zftalk ;-) – la_f0ka Jun 01 '11 at 17:48
  • No problem @la_f0ka glad I could be of some help, also welcome to the Doctrine community – Garry Jun 01 '11 at 17:55
  • @Garry I'm sorry but I'm brain damaged, how do you create an object from the models inside the modules/default/models/Doctrine folder? Or should I use this somehow creating an instance of Doctrine? – la_f0ka Jun 01 '11 at 18:11
  • @la_f0ka `$this->_candidateTable = Model_Doctrine_YourTable::getInstance();` – Garry Jun 01 '11 at 18:36
  • is it to be declared somewhere that you're using the models from /modules/default/models/Doctrine ? Because I'm getting an exception with that `Fatal error: Class 'Model_Doctrine_PlanillaUsersTable' not found in /home/fiodorovich/public_html/gisele/application/controllers/IndexController.php on line 18` – la_f0ka Jun 01 '11 at 21:31
  • No there is nothing in my setup to specify the models in doctrine. ZF should be able to autoload them. Can you load other models ok? – Garry Jun 01 '11 at 21:52
  • @Garry I can load the ones in the application/models directory. That's why I asked – la_f0ka Jun 01 '11 at 21:58
  • @la_f0ka I take it this line is causing the error `$this->_candidateTable = Model_Doctrine_PlanillaUsersTable::getInstance();`? – Garry Jun 01 '11 at 22:03
  • @Garry I'm actually testing it from the index controller like this `$u = new Model_Doctrine_Base_Pais(); //base or not base gives me the same error` and then I var_dump that. – la_f0ka Jun 01 '11 at 22:07
  • @la_f0ka Sorry have to go now, I will have a think about this in the morning. – Garry Jun 01 '11 at 22:10
  • @Garry, don't you worry. I'll keep trying and let you know here if I found the solution. I probably just need to declare somewhere that I'll be using modules or something like that. – la_f0ka Jun 01 '11 at 22:11
  • @Garry,...I finally got it to work! The problem was that I had previously hardcoded model as a resource type in the bootstrap pointing to the application/models folder. I changed that to the modules subfolder and it worked! I guess you can only load models from one place in ZF – la_f0ka Jun 02 '11 at 01:24