Tuning Zend Framework + Doctrine

Cross the two "beasts"



In principle, to mix Zend Framework with Doctrine is not too difficult. But first talk about the preparatory work. According to the author, the default folder structure of Zend Framework projects can be a little more optimal.

It looks like the file structure of the project Zend Framework by default:

/
application/
default/
controllers/
layouts/
models/
views/
html/
library/


Often it may be that application you will have a few (for example, frontend/ and backend/), and the model you will use the same. In this case it would be reasonable to make your models/ folder in library/, in this case the new structure would look as follows:

/
application/
default/
controllers/
layouts/
views/
html/
library/
Model/


In addition, as can be seen, the models folder was renamed in the Model. Then act so.

    the
  1. Download a fresh copy of the distribution Doctrine-x.x.x-Sandbox.tgz official website.
  2. the
  3. Contents of the lib folder from the archive copy to folder library/ our project.
  4. the
  5. Create in root of our project another folder bin/sandbox/ and copied the contents of most files (except the folder is models/ and the file index.php — we don't need them).


Now the files in our project should look something like this:

/
application/
default/
controllers/
layouts/
views/
bin/
sandbox/
data/
lib/
migrations/
schema/
config.php
doctrine
doctrine.php
html/
library/
Doctrine/
Model/
Doctrine.php


Folder bin/sandbox/lib/ clear from the content library we are now in a different place.

It's time to configure Doctrine to work within the new file structure.

Change the value of the constant MODELS_PATH in the file bin/sandbox/config.php on:

SANDBOX_PATH . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'Model'


Next, change the connection settings to the database. Change the value of the constant DSN that you want. For example, if you use MySQL, the DSN may look like this:

'mysql://root:123@localhost/mydbname'


Configure include_paths the first deferral in the config file, so our script can find files in the new location:

set_include_path( '.' . PATH_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . PATH_SEPARATOR . '.' . DIRECTORY_SEPARATOR . 'lib' . PATH_SEPARATOR . get_include_path());


Next, plug in the main library file Doctrine, immediately after the installation paths, and set the autorun function:

<?php
require_once 'Doctrine.php';

/**
* Setup autoload function
*/
spl_autoload_register( array(
'Doctrine',
'autoload'
));
?>


Ie, in General, our configuration should look something like this:

<?php
set_include_path( '.' . PATH_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . PATH_SEPARATOR . '.' . DIRECTORY_SEPARATOR . 'lib' . PATH_SEPARATOR . get_include_path());

require_once 'Doctrine.php';

/**
* Setup autoload function
*/
spl_autoload_register( array(
'Doctrine',
'autoload'
));

define('SANDBOX_PATH', dirname(__FILE__));
define('DATA_FIXTURES_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'fixtures');
define( 'MODELS_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'Model');
define('MIGRATIONS_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . 'migrations');
define('SQL_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'sql');
define('YAML_SCHEMA_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . 'schema');
define('DB_PATH', SANDBOX_PATH . DIRECTORY_SEPARATOR . 'sandbox.db');
define('DSN', 'mysql://root:123@localhost/mydbname');

Doctrine_Manager::connection( DSN, 'sandbox');

Doctrine_Manager::getInstance()->setAttribute('model_loading', 'conservative');
?>


Now we will focus on a very interesting moment.

The fact is that Doctrine does not generate set() and get() methods for object properties, and uses automatic methods __get() and __set(). And since the properties themselves are hidden in one of the properties of the parent class, then no development environment, you do not ever tell them autocomplete. But it's just an inconvenience from which we can easily get rid of, and to obtain some additional facilities. Now we'll show you how to do it.

the

Tune Doctrine Sandbox



In the delivery console application to Doctrine includes a class Doctrine_Cli, which, in fact, implements its functionality. We pranasleep it and this functionality will expand in the following way. We will create a class SandboxCli:
<?php

/**
* Class SandboxCli
* Extends the default Doctrine Client functionality
*
* @package Sandbox
*/
class SandboxCli extends Doctrine_Cli {

/**
* Public  function  to run the loaded task with a given argument
*
* @param array $args
* @return void
*/
public function run( $args) {
ob_start();
parent::run( $args);
$msg = ob_get_clean();
$this- > _chmod();

if (isset( $args[1]) && ($args[1] == 'generate-models-yaml')) {
$this->_genBaseClasses();
$this->_genSgMethods();
$this- > _chmod();
}
echo $msg;
}

/**
* Automatically creates base table and record classes if they are not exists
*
* @param void
* @return void
*/
protected function _genBaseClasses() {
$dir = $this- > _config['models_path'] . DIRECTORY_SEPARATOR . The 'Base' . DIRECTORY_SEPARATOR;
if (!is_dir( $dir)) {
mkdir( $dir);
}
if (!file_exists( $dir . 'Table.php')) {
file_put_contents( $dir . 'Table.php', 'load( $this->_config['yaml_schema_path'] . DIRECTORY_SEPARATOR . 'schema.yml', 'yml');

foreach ($result as $class = > $data) {
require_once $this- > _config ['models_path'] . DIRECTORY_SEPARATOR . $class . '.php';
$rClass = new ReflectionClass( $class);
foreach ($data ['columns'] as $column = > $options) {
$methods = $this->_buildMethodName( $column);
foreach ($methods as $k => $name) {
if (! $rClass- > hasMethod( $name)) {
$this->_addMethod( $class, $name, $column, $k, $options ['type']);
}
}
}
$this->_fixParents( $class);
$this->_createTableClass( $class);
}
}

/**
* Fixes for parent base classes from Doctrine_Record to Model_Base_Record
*
* @param  string  $class - original class name
* @return void
*/
protected function _fixParents($class) {
$dir = $this- > _config['models_path'] . DIRECTORY_SEPARATOR . 'generated' . DIRECTORY_SEPARATOR;
$baseClass = 'Base' . $class;
if (file_exists( $dir . $baseClass . '.php')) {
$content = file_get_contents( $dir . $baseClass . '.php');
$content = preg_replace( '/extends\s+Doctrine_Record\s+{/is', 'extends Model_Base_Record {', $content);
file_put_contents( $dir . $baseClass . '.php', $content);
}
}

/**
* Creates table classes if they have not been already exist
*
* @param string $class - original class name
* @return void
*/
protected function _createTableClass( $class) {
$dir = $this- > _config['models_path'] . DIRECTORY_SEPARATOR . 'Tables' . DIRECTORY_SEPARATOR;
if (!is_dir( $dir)) {
mkdir( $dir);
}
$tblClass = $class . 'Table';
if (! file_exists( $dir . $tblClass . '.php')) {
$content = "_config ['models_path'] . DIRECTORY_SEPARATOR . $class . '.php');

$propType = $this->_type2php( $propertyType);

if ($methodType == 'get') {
$comment = "Returns a value of '$propertyName' field";
$args = ";
$implementation = "return \$this->$propertyName;";
$prms = 'void';
$rets = "$propType \$$propertyName, $propertyType";
} elseif ($methodType == 'set') {
$comment = "Sets '$propertyName' field to a given value";
$args = ' $' . $propertyName;
$implementation = '$this->' . $propertyName . '= $' . $propertyName . ';
return $this;';
$prms = $args;
$rets = $class;
} else {
return;
}

$addCode = " /**
* $comment
*
* @param $prms
* @return $rets
*/
public function $methodName($args) {
$implementation
}

";

$content = preg_replace( '/(class\s+' . preg_quote( $class) . '\s+.*?\{.*?)(\})([^}]*)$/is', '$1' . $addCode . '$2$3', $content);
file_put_contents( $this- > _config['models_path'] . DIRECTORY_SEPARATOR . $class . '.php', $content);
}

/**
* Returns PHP type from YAML definition type
*
* @param string $type - type YAML
* @return string PHP type
*/
protected function _type2php( $type) {
$type = explode ( '(', $type );
$type = $type [0];

$types = array(
'boolean' = > 'bool',
'integer' = > 'int',
'float' => 'float',
'decimal' => 'float',
'string' => 'string',
'array' => 'array',
'object' = > 'string',
'blob' = > 'string',
'clob' = > 'string',
'timestamp' => 'string',
'time' => 'string',
'date' = > 'string',
'enum' => 'string',
'gzip' => 'string'
);

return $types[$type];
}

/**
* Builds method names from a property name
*
* @param string $column_name - the original property name
* @return array
*/
protected function _buildMethodName($column_name) {
$method = preg_split( '/_+/', $column_name, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($method as $k = > $part) {
$method [$k] = ucfirst( $part);
}
$method = join( ", $method);
$return = array(
'get' => "get$method",
'set' => "set$method"
);
return $return;
}

/**
* Fixes group permissions for generated files
*
* @param void
* @return void
*/
protected function _chmod() {
$cmd = 'chmod-R g+w' . MODELS_PATH;
echo `$cmd`;
}

}
?>


And put it in the bin/folder sandbix/lib/.

Well, our extra functionality ready. What it gives us:

the
    the
  • Automatically generates base classes for objects and tables of records, which you can edit by hand (you don't want to rule Doctrine_Table and Doctrine_Record, isn't it?). This is useful if you want to expand their functionality. For example, you can implement logging of all changes of entries in the DB tables — and this is the place.
  • the
  • Automatically creates all the necessary classes of the tables that we created are inherited from the base class.
  • the
  • Automatically adds the methods getProperty() and setProperty( $property) all the properties of classes of records. Now you have to be to work autocomplete if you use for Zend Studio, and will be able to extend the functionality of the methods to access the class properties as you wish.

As you can see, this simple solution significantly improves the flexibility of framing your application, and also not preventing the update of the libraries themselves.

Make Sandbox to work with our client. Modify the file bin/sandbox/doctrine.php:
<?php
require_once('config.php');
require_once 'SandboxCli.php';

// Configure Doctrine Cli
// Normally these are arguments to the cli tasks but if they are set here the arguments will be auto-filled
$config = array('data_fixtures_path' => DATA_FIXTURES_PATH,
'models_path' = > MODELS_PATH,
'migrations_path' => MIGRATIONS_PATH,
'sql_path' = > SQL_PATH,
'yaml_schema_path' => YAML_SCHEMA_PATH);

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


Voila! Can experience. Create in your database multiple related tables, e.g.:



And run the command:

./doctrine generate-yaml-db
./doctrine generate-models-yaml


In the future, you can use the second command to update your models.

Check all the necessary files in the library folder/Model/.

the

Tune the Zend Framework to work with the new model



First, create the folder application/default/run/ and a file bootstrap.php and move the contents of the file html/index.php. And in the file html/index.php write:

require '..' . DIRECTORY_SEPARATOR . 'application' . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . 'run' . DIRECTORY_SEPARATOR . 'bootstrap.php';


This will make it impossible to view the code, even if there is a failure of the web server. In the worst case we will see only the connection to the other file.

Now we will make the necessary changes in our bootstrap.php it must look like the following:

<?php
setAttribute( Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);

/**
* Turn on all Doctrine validators
*/
Doctrine_Manager::getInstance()->setAttribute( Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);

/**
* Setup the Doctrine connection
*/
Doctrine_Manager::connection( 'mysql://root:123@localhost/mydbname');

/**
* Set the model loading to conservative/lazy loading
*/
Doctrine_Manager::getInstance()->setAttribute( 'model_loading', 'conservative');

/**
* Load the models for the autoloader
*/
Doctrine::loadModels( '..' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'Model');

/**
* Setup controller
*/
$controller = Zend_Controller_Front::getInstance();
$controller- > setControllerDirectory( '../application/default/controllers');
$controller->throwExceptions( true); // should be turned on in development time 

/**
* bootstrap layouts
*/
Zend_Layout::startMvc( array(
'layoutPath' = > '../application/default/layouts',
'layout' => 'main'
));

/**
* Run a front controller
*/
$controller->dispatch();
?>


All, we crossed two "beasts". can now try out our model in action, for example, in application/default/controllers/IndexController.php:

<?php
public function indexAction() {
$artist = new Artist();
$artist- > setName( 'DDT')
->setDescription( 'Very cool russian rock-band')
->save();

$artist = Doctrine::getTable( 'Artist')- > find( 1); 

echo '<pre>';
print_r( $artist);
echo '</pre>';
}
?>


You can download the full example source (a 4.53 MB)

PS Cross-post from the blog author: mikhailstadnik.com/tuning-zf-with-doctrine
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

ODBC Firebird, Postgresql, executing queries in Powershell

garage48 for the first time in Kiev!

The Ministry of communications wants to ban phones without GLONASS