Zend Framework 1 and Doctrine 2 integration – modular setup

Elink Media posts a followup;

I’ve created a new branch on my Github project “zf1-doctrine2″. The “modular_setup” branch shows how we could setup Zend Framework with the modular approach, while still be able to use Doctrine 2 as the ORM for each module.

Oh well, the idea is to make each module decoupled with the rest of the application, so really, you could use whatever database handling mechanism in each module.

Check it out from my Github project here.

Reference

via Zend Framework 1 and Doctrine 2 integration – modular setup.

Adding Zend_Cache to Flex/Flash Builder 4 Projects

I have som rather large and time consuming queries running in the Statistics screen of an NOC (Network Operations Center) Flex/Flash Builder 4 application i’we been tinkering with, to prevent the database server to be boggen down by multiple queries fired by this app in multiple places I had to implement caching.

And to do this is alot easier than it might sound like especially for the (PHP) Zend_AMF based services.

Once you have setup your Data Centric client/server  connection like it’s described in this Article @ DevZone you begin by editing the gateway.php;

// Store configuration in the registry
Zend_Registry::set("amf-config", $amf);

// Configure Zend_Cache
$frontendOptions = array(
'lifetime' => 12*3600,
'automatic_serialization' => true,
'default_options' => array(
'cache_with_get_variables' => true,
'cache_with_post_variables' => true,
'cache_with_session_variables' => true,
'cache_with_files_variables' => true,
'cache_with_cookie_variables' => true,
'make_id_with_get_variables' => true,
'make_id_with_post_variables' => true,
'make_id_with_session_variables' => true,
'make_id_with_files_variables' => true,
'make_id_with_cookie_variables' => true
)
);
$backendOptions = array(
'cache_dir' => '/tmp/'
);

$cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);

// Store cache configuration in the registry
Zend_Registry::set("cache", $cache);

Then modify your service class to look similar to this;

public function getCustomerStatsByMonth() {
// Get the Cache from Registry
$cache = Zend_Registry::get("cache");
$id = 'getCustomerStatsByMonth';
if(!($rows = $cache->load($id)))
{
// We didnt find anything in the cache so lets get it from DB
$stmt = mysqli_prepare($this->connection, "SELECT
customers.name,
SUBSTR(FROM_UNIXTIME(`cdrs`.`start`),1,7) AS `month`,
outgroups.name_invoices,
ROUND(SUM(`cdrs`.`talktime`)/60) AS `minutes`,
COUNT(`cdrs`.`start`) AS `calls`
FROM
es.outgroups
INNER JOIN es.cdrs
ON (outgroups.id = cdrs.outgroup)
INNER JOIN es.customers
ON (customers.id = cdrs.scustomer)
WHERE (outgroups.name_invoices LIKE 'Sweden%' AND cdrs.start > UNIX_TIMESTAMP('2010-01-01 00:00:00') AND cdrs.status = 'answer' AND cdrs.talktime > 0 AND custome\
rs.parent IN (7,42))
GROUP BY customers.id,SUBSTR(FROM_UNIXTIME(`cdrs`.`start`),1,7),outgroups.name_invoices
ORDER BY SUBSTR(FROM_UNIXTIME(`cdrs`.`start`),1,7),outgroups.name_invoices;");
$this->throwExceptionOnError();

mysqli_stmt_execute($stmt);
$this->throwExceptionOnError();

$rows = array();

mysqli_stmt_bind_result($stmt, $row->name, $row->month, $row->name_invoices, $row->minutes, $row->calls);

while (mysqli_stmt_fetch($stmt)) {
$row->month = new DateTime($row->month.'-01 00:00:00');
$rows[] = $row;
$row = new stdClass();
mysqli_stmt_bind_result($stmt, $row->name, $row->month, $row->name_invoices, $row->minutes, $row->calls);
}

mysqli_stmt_free_result($stmt);
mysqli_close($this->connection);
// Save collected rows into the cache
$cache->save($rows,$id,array('customer_stats'),3*3600);
}
return $rows;
}

Your queries will now be cached after the first time you run them for the specified amount of time (TTL), and boom you’re done. If you require the update function in your service classes to purge the cache simply insert;

// To remove or invalidate in particular cache id, you can use the remove() method :
$cache->remove('idToRemove');

Read more about cache cleaning in the Zend Cache reference.

Hope this little article helps and please comment if you guys have better suggestions.
(Yes I know I can use APC & Memcached, but in this example I didnt have to 🙂 )

Regards
Danny Froberg

Doctrine Tricks — SoftDelete

While pouring over some posts about Doctrine I stumbled upon a very nice solution to the Cascading Delete issue when using the SoftDelete behaviour on an Doctrine Model. Here is what the guys at Elink Media writes;

I have been using Doctrine ORM for a while now. Here I want to discuss 2 tricky issues I have encountered and resolved.

First, I’m paranoid of deleting records from the database. Things like registered users, I would rather have them marked as “DELETED”, but still keep the record in the database, just in case …

With Doctrine, the SoftDelete behaviour is perfect for this purpose. When a model is enabled with the SoftDelete behaviour, an extra field “deleted_at” is automatically added to the corresponding database table by default. So, when you call the delete() function on a model object, the corresponding data row in the database receives a timestamp on the “deleted_at” field. Data having values on the “deleted_at” field will not be returned by any find methods in the future.

It all sounds good, right? Not until you start trying to cascade “delete” on associations. Since we are not really deleting the record, DBMS level delete cascading will simply not work (In Doctrine YAML schema term, the “onDelete: CASCADE” will not work). Now, I’ll illustrate the problem and the solution with a typical blog example.

Click for Solution 🙂

http://blog.elinkmedia.net.au/2010/01/30/doctrine-tricks/.

Setting Up Doctrine for Zend Framework 1.9.x

After some fiddling and googling and Zendcast watching 🙂 I figured out how to get the models to generate the way I needed them to.
And figuring out why generate-sql and build-all-reload did not create sql schema nor create any tables in the mysql database.

Directory Structure

Standard ZF directory structure with a few addition (Can’t wait for ZFTool to do all this for us)

├───application
│   ├───configs
│   │   ├───data          < --
│   │   │   ├───fixtures <--
│   │   │   └───sql        <--
│   │   └───migrations  <--
│   ├───controllers
│   ├───models
│   ├───scripts            <-- doctrine.php & bat here
│   └───views
├───library
│   ├───Doctrine        <-- Please use the latest 1.2.x
│   ├───vendor         <-- Do not forget this one or things will be bad.
├───public

 

application.ini

Only applicable parts added.
[production]
phpSettings.date.timezone = "Europe/Stockholm"
autoloaderNamespaces[] = "Doctrine"
doctrine.dsn = "mysql:[email protected]/testbench"
doctrine.data_fixtures_path = APPLICATION_PATH "/configs/data/fixtures"
doctrine.sql_path = APPLICATION_PATH "/configs/data/sql"
doctrine.migrations_path = APPLICATION_PATH "/configs/migrations"
doctrine.yaml_schema_path = APPLICATION_PATH "/configs/schema.yml"
doctrine.models_path = APPLICATION_PATH "/models"

Bootstrap.php

Please note the inline comment about model loading attributes, this is what broke it for me, for some reason using conservative will prevent generation and creation of database tables and schemas, although model generation works fine, very puzzling!.

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {

protected function _initDoctrine() {
$this->getApplication()
->getAutoloader()
->pushAutoloader ( array ('Doctrine', 'autoload' ) );
//spl_autoload_register ( array ('Doctrine', 'modelsAutoload' ) );
$manager = Doctrine_Manager::getInstance ();
$manager->setAttribute ( Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true );
// The Model Loading acts a tad weird - Use Default for now.
//$manager->setAttribute ( Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_AGGRESIVE); // MODEL_LOADING_CONSERVATIVE
$manager->setAttribute ( Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, false );

$doctrineConfig = $this->getOption('doctrine');
$conn = Doctrine_Manager::connection($doctrineConfig['dsn'],'doctrine');
$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM,true);

}
}

doctrine.bat

Since this dev machine is on Windows XP:
@echo off
echo Running Doctrine CLI.
"C:\Zend\ZendServer\bin\php.exe" -f C:\Zend\Apache2\htdocs\testbench\application\scripts\doctrine.php %1 %2 %3 %4 %5 %6 %7 %8 %9

doctrine

If you are linux/mac based: (Dont forget to chmod +x it)

#!/usr/bin/env php
< ?php chdir(dirname(__FILE__)); include('doctrine.php');

doctrine.php

Please note the inline comments in this one and customize it to your own liking.

< ?php /** * Doctrine CLI */ error_reporting(E_ALL); define('ROOT_PATH', realpath(dirname(__FILE__))); define('APPLICATION_PATH', realpath(dirname(__FILE__) . "/../")); define('APPLICATION_ENV', 'development'); //Ensure library/ is on include_path set_include_path(implode(PATH_SEPARATOR, array( '../library',get_include_path() ))); /** Zend_Application */ require_once 'Zend/Application.php'; // Create application, bootstrap, and run $application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini' ); // Read in the application.ini bootstrap for Doctrine $application->getBootstrap()->bootstrap('doctrine');

// Create the configuration array
$config = $application->getOption('doctrine');
// (Note you can have all of these in application.ini aswell)
$config['generate_models_options'] = array(
// Define the PHPDoc Block in the generated classes
'phpDocPackage' =>'TestBench',
'phpDocSubpackage' =>'Models',
'phpDocName' =>'Danny Froberg',
'phpDocEmail' =>[email protected]',
'phpDocVersion' =>'1.0',
// Define whats what and named how, where.
'suffix' => '.php',
'pearStyle' => true,
'baseClassPrefix' => 'Base_',
// Unless you have created a custom class or want Default_Model_Base_Abstract
'baseClassName' => 'Doctrine_Record',
// Leave this empty as specifying 'Base' will create Base/Base
'baseClassesDirectory' => NULL,
// Should make it Zend Framework friendly AFAIK
'classPrefix' => 'Default_Model_',
'classPrefixFiles' => false,
'generateBaseClasses' => true,
'generateTableClasses' => false,
'packagesPath' => APPLICATION_PATH . '/models',
'packagesFolderName' => 'packages',

);

$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);
?>

In the next article we'll take it for a test spin and generate a few models and such 😉

Enjoy.