📜 ⬆️ ⬇️

Zend Framework Tuning + Doctrine

We cross two "animals"



Basically, crossing Zend Framework with Doctrine is not that difficult. But first, let's talk about the preparatory work. According to the author, the default file structure of the Zend Framework project can be made slightly more optimal.

This is the default file structure for the Zend Framework project:
')
  /
   application /
     default /
       controllers /
       layouts /
       models /
       views /
   html /
   library / 


Often it may turn out that you will have several applications (for example, frontend / and backend /), and you will use the same model. In this case it would be reasonable to move your models / to the library / folder, in this case the new structure would look like this:

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


In addition, as can be seen, the models / folder was renamed to Model. Further we act like this.

  1. Download the latest distribution Doctrine-xxx-Sandbox.tgz from the official site .
  2. Copy the contents of the lib / folder from the archive into the library / folder of our project
  3. We create another bin / sandbox / folder in the root of our project and copy the rest of the archive into it (except for the models / folder and the index.php file - we don’t need them).


Now the files of our project should look like this:

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


We clear the bin / sandbox / lib / folder from the contents - the library is now in another place.

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

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

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


Next, change the settings for connecting to the database. Change the value of the DSN constant to what is needed. For example, if you are using MySQL DBMS, the DSN might look like this:

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


Configuring include_paths with the first urgent in the config so that our scripts can find files in new places:

  set_include_path ('.'. PATH_SEPARATOR. '...' 


Next, we include the main Doctrine library file, immediately after installing the paths, and install the autoload function:

  <? php
 require_once 'Doctrine.php';

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


Ie, in general, our config should look something like this:

  <? php
 set_include_path ('.'. PATH_SEPARATOR. '...'

 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 point.

The fact is that Doctrine does not generate set () and get () methods for object properties, but uses automatic methods __get () and __set (). And since the properties themselves are hidden within a single class-parent property, no development environment will ever tell you in autocomplex. But this is just an inconvenience from which we can easily get rid of, and plus to this get some additional amenities. Now we will demonstrate how to do it.

Tuning Doctrine Sandbox



The Doctrine class for the console application for Doctrine includes the Doctrine_Cli class, which, in fact, implements its functionality. We will inherit it and expand this functionality as follows. Create your own SandboxCli class:

  <? php

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

	 / **
	  * Public function
	  *
	  * @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;
	 }

	 / **
	  If they are not there
	  *
	  * @param void
	  * @return void
	  * /
	 protected function _genBaseClasses () {
		 $ dir = $ this -> _ config ['models_path'].  DIRECTORY_SEPARATOR.  '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 parent to base_class_Record 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
	  *
	  * @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 - YAML type
	  * @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 - 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 / sandbix / lib / folder.

Great, our extra functionality is ready. What does it give us:


As you can see, such an uncomplicated solution significantly improves the flexibility of the framework of your application, and also does not preclude updating the libraries themselves.

Now we’ll get Sandbox to work with our client. Correct the file bin / sandbox / doctrine.php:

  <? php
 require_once ('config.php');
 require_once 'SandboxCli.php';

 // Configure Doctrine Cli
 This 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! We can try. Create several related tables in your database, for example:



And run the commands:

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


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

Check if all necessary files are created in the library / Model / folder.

Tuning the Zend Framework to work with a new model



First of all, let's create the application / default / run / folder and the bootstrap.php file in it, and transfer the contents of the html / index.php file to it. And in the file html / index.php we will 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 the web server crashes. In the worst case, you will only see the connection of another file.

Now we’ll make the necessary changes to our bootstrap.php, it should look something like this:

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

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

 / **
  * Setup 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 front controller
  * /
 $ controller-> dispatch ();
 ?> 


Everything, we crossed two "animals". Now we can try 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 in source codes (4.53 MB)

PS Cross-post from the author's blog: mikhailstadnik.com/tuning-zf-with-doctrine

Source: https://habr.com/ru/post/46084/


All Articles