فهرست منبع

Inicio de las migraciones

gabriel 8 سال پیش
کامیت
13d4c5ab91
4فایلهای تغییر یافته به همراه669 افزوده شده و 0 حذف شده
  1. 577 0
      Migrations/MigrationsBase.php
  2. 9 0
      MigrationsBundle.php
  3. 73 0
      README.md
  4. 10 0
      composer.json

+ 577 - 0
Migrations/MigrationsBase.php

@@ -0,0 +1,577 @@
+<?php
+
+namespace Migrations;
+
+use Doctrine\DBAL\Migrations\AbstractMigration;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Clase de migracion de base.
+ * Orden de ejecucion de metodos.
+ * 1- preUp
+ * 2- up
+ * 3- postUp
+ * 4- preDown
+ * 5- down
+ * 6- postDown
+ */
+class MigrationsBase extends AbstractMigration implements ContainerAwareInterface
+{
+	/**
+	 * Tipo de senetencia sql insert.
+	 */
+	const INSERT = "INSERT";
+	/**
+	 * Tipo de sentencia sql update.
+	 */
+	const UPDATE = "UPDATE";
+	/**
+	 * Tipo de sentenca sql delete.
+	 */
+	const DELETE = "DELETE";
+	/**
+	 * @var int $line Contiene el numero de linea que estoy ejecutando.
+	 */
+	private $line = 0;
+	/**
+	 * @var bool Me dice si tengo que mostrar los parametros en las consultas.
+	 */
+	private $showParameters = false;
+	/**
+	 * @var array $errorLineExecution Contiene una descripcion del error que ocaciono una linea del script YAML.
+	 */
+	private $errorLineExecution = array();
+	/**
+	 * @var array $lineExecution Contiene una descripcion de como se ejecuto cada linea del script YAML.
+	 */
+	private $lineExecution = array();
+	/**
+	 * @var ContainerInterface $container Contiene el contenedor.
+	 */
+	private $container;
+
+	/**
+	 * @param ContainerInterface|null $container Contiene un objeto que implementa "ContainerInterface".
+	 * @return ContainerInterface Retorna un objeto que implementa la interfaz "ContainerInterface".
+	 */
+	public function setContainer(ContainerInterface $container = null)
+	{
+		$this->container = $container;
+		return $this->container;
+	}
+
+	/**
+	 * @return ContainerInterface Retorna un objeto que implementa la interfaz "ContainerInterface".
+	 */
+	public function getContainer()
+	{
+		return $this->container;
+	}
+
+	/**
+	 * @return bool Retorna el valor de la variable showParameters.
+	 */
+	public function isShowParameters()
+	{
+		return $this->showParameters;
+	}
+
+	/**
+	 * @param bool $showParameters Si esta en TRUE muestra los parametros en las consultas.
+	 */
+	public function setShowParameters($showParameters)
+	{
+		$this->showParameters = $showParameters;
+	}
+
+	/**
+	 * @return int Retorna el numero de linea que estoy analizando.
+	 */
+	public function getLine()
+	{
+		return $this->line;
+	}
+
+	/**
+	 * @param int $line Setea el numero de linea.
+	 */
+	private function setLine($line)
+	{
+		$this->line = $line;
+	}
+
+	/**
+	 * Funcion que suma 1 a la variable line.
+	 */
+	private function sumLine()
+	{
+		$this->line = $this->line + 1;
+	}
+
+	/**
+	 * @return array Retorna un array con las lineas que produjeron errores.
+	 */
+	public function getErrorLineExecution()
+	{
+		return $this->errorLineExecution;
+	}
+
+	/**
+	 * @param array $errorLineExecution Setea un array con las lineas que produjeron errores.
+	 */
+	public function setErrorLineExecution($errorLineExecution)
+	{
+		$this->errorLineExecution = $errorLineExecution;
+	}
+
+	/**
+	 * Agrega una linea de error.
+	 * @param string $type Contiene el tipo de sentencia sql.
+	 * @param \Throwable $ex Contiene una excepcion.
+	 */
+	private function addErrorLineExecution($type, \Throwable $ex)
+	{
+		if (!array_key_exists($type, $this->errorLineExecution)) {
+			$this->errorLineExecution[$type] = array();
+		}
+		array_push($this->errorLineExecution[$type], "Line: " . $this->getLine() . " => [" . $ex->getCode() . "] = " . $ex->getMessage());
+	}
+
+	/**
+	 * @return array Retorna una array con el valor de la ejecucion de cada linea.
+	 */
+	public function getLineExecution()
+	{
+		return $this->lineExecution;
+	}
+
+	/**
+	 * @param array $lineExecution Setea un array con las lineas de ejecucion.
+	 */
+	public function setLineExecution($lineExecution)
+	{
+		$this->lineExecution = $lineExecution;
+	}
+
+	/**
+	 * Agrega el valor de la ejecucion.
+	 * @param string $type Contiene el tipo de sentencia sql.
+	 * @param string $value Contiene el valor de la ejecucion.
+	 */
+	private function addLineExecution($type, $value)
+	{
+		if (!array_key_exists($type, $this->lineExecution)) {
+			$this->lineExecution[$type] = array();
+		}
+		array_push($this->lineExecution[$type], "Line: " . $this->getLine() . " => " . $value);
+	}
+
+
+	/**
+	 * Realiza un up de la modificaciones DDL. Siempre son agregados.
+	 * Para realizar sentencias DML utilizarlos metodos preUp y postUp.
+	 * @param Schema $schema Contiene el objeto esquema.
+	 */
+	public function up(Schema $schema)
+	{
+	}
+
+	/**
+	 * Realiza un up de la modificaciones DDL. Siempre son eliminacion.
+	 * Para realizar sentencias DML utilizarlos metodos preDown y postDown.
+	 * @param Schema $schema Contiene el objeto esquema.
+	 */
+	public function down(Schema $schema)
+	{
+	}
+
+	/**
+	 * Procesa un yaml para generar las sentencias DML que luego seran ejecutadas en la base de datos.
+	 * El directorio origen es DoctrineMigrations en adelante.
+	 * @param string $fileName Contiene el  nombre del archivo a incorporar.
+	 */
+	protected function interpretYaml($fileName)
+	{
+		// obtengo el directorio de trabajo
+		$dir = dirname(__DIR__) . "/DoctrineMigrations/";
+		// leo el yaml
+		$value = $this->readYaml($dir, $fileName);
+		if ($value != null && count($value) > 0) {
+			// paso las key a mayusculas
+			foreach ($value as $key => $val) {
+				unset($value[$key]);
+				$value[strtoupper($key)] = $val;
+			}
+			// reemplazo las keys que poseen importkey
+			$value = $this->replaceImportsKey($dir, $value);
+			// reemplazo los valores que poseen import
+			$value = $this->replaceImportsValue($dir, $value);
+
+			// creo los insert
+			if (array_key_exists(MigrationsBase::INSERT, $value)) {
+				$this->setLine(0);
+				$this->createInserts($value[MigrationsBase::INSERT]);
+			}
+			// creo los update
+			if (array_key_exists(MigrationsBase::UPDATE, $value)) {
+				$this->setLine(0);
+				$this->createUpdates($value[MigrationsBase::UPDATE]);
+			}
+			// creo los delete
+			if (array_key_exists(MigrationsBase::DELETE, $value)) {
+				$this->setLine(0);
+				$this->createDeletes($value[MigrationsBase::DELETE]);
+			}
+		}
+	}
+
+	/**
+	 * Crea los insert a partir de una estructura yaml. Se puede utilizar la palabra clave "ignore", "replace" o "orupdate".
+	 * El "replace" sobreescribe al "ignore" y el "orupdate" sobreescribe al "replace".
+	 * @param array $arrayInsert Contiene la estructura yaml para los insert.
+	 */
+	private function createInserts($arrayInsert)
+	{
+		foreach ($arrayInsert as $table => $inserts) {
+			// recorro las tablas
+			foreach ($inserts as $key => $valueKey) {
+				// recorro cada uno de los insert que quiero hacer
+				// almacena los campos
+				$fields = "";
+				// almacena el valor de los campos
+				$valuesFields = "";
+				// me dice si tengo que utilizar la palabra ignore
+				$ignore = " ";
+				// contiene la primer palabra de la sentencia (INSERT/REPLACE)
+				$insert = "INSERT";
+				// me dice si es un insert or update
+				$orUpdate = false;
+				// contiene los valores del insert or update
+				$orUpdateValues = "";
+				// contiene los valores para el bind del stament
+				$arrayPrepare = array();
+				foreach ($valueKey as $field => $value) {
+					// recorro los datos a insertar
+					$field = strtolower(trim($field));
+					$value = trim($value);
+					if (strlen($field) > 0 && strlen($value) > 0) {
+						if ($field === 'ignore') {
+							$value = strtolower($value);
+							if ($value === '1' || $value === 'true') {
+								if ($insert === 'INSERT') {
+									$ignore = " IGNORE ";
+								}
+							}
+						} else if ($field === 'replace') {
+							$value = strtolower($value);
+							if ($value === '1' || $value === 'true') {
+								$insert = "REPLACE";
+								$ignore = " ";
+							}
+						} else if ($field === 'orupdate') {
+							$value = strtolower($value);
+							if ($value === '1' || $value === 'true') {
+								$orUpdate = true;
+								$ignore = " ";
+								$insert = "INSERT";
+							}
+						} else {
+							$arrayPrepare[':' . $field] = $value;
+							$fields = $fields . $field . ", ";
+							$valuesFields = $valuesFields . ":" . $field . ", ";
+							$orUpdateValues = $orUpdateValues . $field . " = " . ":" . $field . ", ";
+						}
+					}
+				}
+				if (strlen($fields) > 1) {
+					$fields = substr($fields, 0, strlen($fields) - 2);
+				}
+				if (strlen($valuesFields) > 1) {
+					$valuesFields = substr($valuesFields, 0, strlen($valuesFields) - 2);
+				}
+				if (strlen($orUpdateValues) > 1) {
+					$orUpdateValues = substr($orUpdateValues, 0, strlen($orUpdateValues) - 2);
+				}
+				if (strlen($fields) > 1 && strlen($valuesFields) > 1) {
+					$sql = $insert . $ignore . "INTO " . $table . " (" . $fields . ") VALUES (" . $valuesFields . ")";
+					if ($orUpdate) {
+						$sql .= " ON DUPLICATE KEY UPDATE " . $orUpdateValues . ";";
+					} else {
+						$sql .= ";";
+					}
+					$this->executeSQL($sql, MigrationsBase::INSERT, $arrayPrepare);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Crea los update a partir de una estructura yaml.
+	 * @param array $arrayInsert Contiene la estructura yaml para los insert.
+	 */
+	private function createUpdates($arrayInsert)
+	{
+		foreach ($arrayInsert as $table => $inserts) {
+			// recorro las tablas
+			foreach ($inserts as $key => $valueKey) {
+				// recorro cada uno de los insert que quiero hacer
+				$set = "";
+				$where = "";
+				// contiene los valores para el bind del stament
+				$arrayPrepare = array();
+				foreach ($valueKey as $field => $value) {
+					// recorro los datos a realizar un update
+					if (strlen(trim($field)) > 0 && strlen(trim($value)) > 0) {
+						if ($field === "where") {
+							$where = $value;
+						} else {
+							$arrayPrepare[':' . $field] = $value;
+							$set = $set . $field . " = :" . $field . ", ";
+						}
+					}
+				}
+				if (strlen($set) > 1) {
+					$set = substr($set, 0, strlen($set) - 2);
+				}
+				if (strlen($set) > 1) {
+					$sql = "UPDATE " . $table . " SET " . $set . " WHERE " . $where . ";";
+					$this->executeSQL($sql, MigrationsBase::UPDATE, $arrayPrepare);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Crea los delete a partir de una estructura yaml.
+	 * @param array $arrayInsert Contiene la estructura yaml para los insert.
+	 */
+	private function createDeletes($arrayInsert)
+	{
+		foreach ($arrayInsert as $table => $inserts) {
+			// recorro las tablas
+			foreach ($inserts as $key => $valueKey) {
+				// recorro cada uno de los insert que quiero hacer
+				$where = "";
+				foreach ($valueKey as $field => $value) {
+					// recorro los datos a realizar un update
+					if (strlen(trim($field)) > 0 && strlen(trim($value)) > 0) {
+						if ($field === "where") {
+							$where = $value;
+						}
+					}
+				}
+				$sql = "DELETE FROM " . $table . " WHERE " . $where . ";";
+				$this->executeSQL($sql, MigrationsBase::DELETE);
+			}
+		}
+	}
+
+	/**
+	 * Obtiene el contenido de un archivo yaml.
+	 * @param string $dir Contiene el directorio de trabajo. Por defecto "DoctrineMigrations".
+	 * @param string $archivo Contiene el  nombre del archivo a incorporar.
+	 * @return bool|string Retorna el contenido del archivo.
+	 */
+	private function readYaml($dir, $archivo)
+	{
+		return Yaml::parse(file_get_contents($dir . $archivo));
+	}
+
+	/**
+	 * Obtiene el contenido de un archivo.
+	 * @param string $dir Contiene el directorio de trabajo. Por defecto "DoctrineMigrations".
+	 * @param string $archivo Contiene el  nombre del archivo a incorporar.
+	 * @return bool|string Retorna el contenido del archivo.
+	 */
+	private function readImportInValues($dir, $archivo)
+	{
+		return file_get_contents($dir . $archivo);
+	}
+
+	/**
+	 * Reemplaza el contenido de los imports dentro de los values.
+	 * @param string $dir Contiene el directorio de trabajo. Por defecto "DoctrineMigrations".
+	 * @param array $valores Contiene el array el contenido del yaml.
+	 * @return array Retorna el array con los valores cambiados.
+	 */
+	private function replaceImportsValue($dir, $valores)
+	{
+		try {
+			foreach ($valores as $key => $value) {
+				if (is_array($value)) {
+					if (count($value) == 1 && array_key_exists("import", $value)) {
+						if (file_exists($dir . $value["import"])) {
+							$valores[$key] = $this->readImportInValues($dir, $value["import"]);
+						} else {
+							$valores[$key] = "FILE NOT FOUND";
+						}
+					} else {
+						$valores[$key] = $this->replaceImportsValue($dir, $value);
+					}
+				}
+			}
+		} catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
+			var_dump($e);
+		}
+		return $valores;
+	}
+
+	/**
+	 * Reemplaza el contenido de los imports dentro de las key.
+	 * @param string $dir Contiene el directorio de trabajo. Por defecto "DoctrineMigrations".
+	 * @param array $valores Contiene el array el contenido del yaml.
+	 * @return array Retorna el array con los valores cambiados.
+	 */
+	private function replaceImportsKey($dir, $valores)
+	{
+		try {
+			foreach ($valores as $key => $value) {
+				if (is_array($value)) {
+					$valores[$key] = $this->replaceImportsKey($dir, $value);
+				} else {
+					if (trim($key) === "importkey") {
+						if (file_exists($dir . $value)) {
+							$valores = $this->readYaml($dir, $value);
+						} else {
+							$valores[$key] = "FILE NOT FOUND";
+						}
+					}
+				}
+			}
+		} catch (\Symfony\Component\Debug\Exception\ContextErrorException $e) {
+			var_dump($e);
+		}
+		return $valores;
+	}
+
+	/**
+	 * Funcion ejecuta el YAML en la base de datos dentro de una transaccion.
+	 * @param string $file Contiene el nombre de archivo a procesar.
+	 */
+	protected function executeYaml($file)
+	{
+		$this->connection->beginTransaction();
+		try {
+			$this->interpretYaml($file);
+			if (count($this->getErrorLineExecution()) > 0
+			) {
+				//se produjeron errores
+				$this->connection->rollBack();
+				echo "Se produjeron errores.";
+			} else {
+				$this->connection->commit();
+				echo "Migracion correcta.";
+			}
+		} catch (\Throwable $e) {
+			$this->connection->rollBack();
+			echo "Se produjeron errores.";
+		}
+	}
+
+	/**
+	 * Funcion que ejecuta una sentencia sql.
+	 * @param string $sql Contiene el sql a ejecutar.
+	 * @param string $type Contiene el tipo de sentencia.
+	 * @param array $arrayPrepare Contiene un array con los valores. La key contiene el valor a
+	 *                            buscar en la sentencia sql y el value es el valor.
+	 */
+	private function executeSQL($sql, $type, $arrayPrepare = null)
+	{
+		$stmt = $this->connection->prepare($sql);
+		$param = "";
+		if ($arrayPrepare != null && count($arrayPrepare) > 0) {
+			foreach ($arrayPrepare as $keyPrepare => $valuePrepare) {
+				$stmt->bindValue($keyPrepare, $valuePrepare);
+				if ($this->isShowParameters()) {
+					$param .= " [" . $keyPrepare . "]=" . $valuePrepare . "    ";
+				}
+			}
+		}
+		if ($this->isShowParameters()) {
+			if (strlen($param) > 4) {
+				$param = " Parameters: " . substr($param, 0, strlen($param) - 4);
+			}
+		}
+		try {
+			$resp = $stmt->execute();
+			$this->addLineExecution($type, ($resp ? "OK - " : "ERROR - ") . $sql .
+				$param);
+		} catch (\Throwable $ex) {
+			$this->addLineExecution($type, "????? - " . $sql . $param);
+			$this->addErrorLineExecution($type, $ex);
+		}
+
+		$this->sumLine();
+	}
+
+	/**
+	 * Borra la migracion de la tabla de migraciones.
+	 * @param string $obj Contiene el objeto this.
+	 */
+	protected function deleteMigrationsVersion($obj)
+	{
+		$arr = explode("\\", get_class($obj));
+		$this->connection->beginTransaction();
+		$stmt = $this->connection->prepare("DELETE FROM migration_versions WHERE migration_versions.version = '" .
+			str_ireplace("version", "", $arr[count($arr) - 1]) . "'");
+		$stmt->execute();
+		$this->connection->commit();
+	}
+
+
+	/**
+	 * Funcion que muestra por pantalla el resultado de la ejecucion y de los errores.
+	 */
+	protected function showResult()
+	{
+		if (count($this->getLineExecution()) > 0) {
+			echo "-----------------------------------------------\n";
+			echo " 	EJECUCIONES\n";
+			echo "-----------------------------------------------\n";
+			foreach ($this->getLineExecution() as $key => $value) {
+				echo $key . "\n";
+				foreach ($this->getLineExecution()[$key] as $k => $v) {
+					echo "\t" . $v . "\n";
+				}
+			}
+		}
+		if (count($this->getErrorLineExecution()) > 0) {
+			echo "-----------------------------------------------\n";
+			echo " 	ERRORES\n";
+			echo "-----------------------------------------------\n";
+			foreach ($this->getErrorLineExecution() as $key => $value) {
+				echo $key . "\n";
+				foreach ($this->getErrorLineExecution()[$key] as $k => $v) {
+					echo "\t" . $v . "\n";
+				}
+			}
+		}
+	}
+
+//-----------------------------------------------------------------------------------------------
+//    EJEMPLOS DE COMO PUEDO REALIZAR CONSULTAR A LA BASE DE DATOS.
+//             SE ACONSEJA HACERLO EN LOS preUp/preDown/postUp/preDown
+//-----------------------------------------------------------------------------------------------
+
+	/**
+	 * Ejemplo de como hacer un select utilizando entidades.
+	 */
+	private function selectEntityManager()
+	{
+		$em = $this->container->get('doctrine.orm.entity_manager');
+		$users = $em->getRepository("BaseUserBundle:User")->findBy(array('enabled' => 1));
+		var_dump($users);
+	}
+
+	/**
+	 * Ejemplo de como realizar una consulta a la base de datos a travez de la conexion.
+	 */
+	private function selectConexion()
+	{
+		$users = $this->connection->executeQuery("SELECT * FROM user");
+		var_dump($users);
+	}
+}

+ 9 - 0
MigrationsBundle.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Migrations;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+class MigrationsBundle extends Bundle
+{
+}

+ 73 - 0
README.md

@@ -0,0 +1,73 @@
+# MigrationsBundle
+
+- [Installation](#installation)
+- [Class](#class)
+
+## Installation
+
+**composer.json**:
+
+```javascript
+"repositories": [
+    {
+        "type": "vcs",
+        "url":  "ssh://git@bitbucket.org/ikflowdat/migrations.git"
+    }
+],
+"require": {
+    "ik/migrations-bundle": "dev-master"
+},
+```
+
+**app/AppKernel.php**:
+
+```php
+public function registerBundles()
+{
+    $bundles = [
+        new OwnerVoterBundle\OwnerVoterBundle()
+    ];
+    .
+    .
+}
+```
+
+**app/config/config.yml**:
+
+```yml
+imports:
+    - { resource: "@ExtraBundle/Resources/config/services.yml" }
+```
+
+## Class
+
+- **Migrations\MigrationsBase**: Clase de la cual se debe extender para poder hacer migraciones interpretando un yaml. 
+Esta clase proporciona los siguientes metodos que se ejecutan en el siguiente orden:
+```Php
+    1- preUp
+    2- up (implementaci&oacute;n obligatoria)
+    3- postUp
+    4- preDown
+    5- down (implementaci&oacute;n obligatoria)
+    6- postDown
+```
+ 	  
+Ademas se pueden utilizar las siguientes funciones:
+```Php
+	* deleteMigrationsVersion: recibe como par&acute;metro el objeto de ejecuci&oacuete;n ($this). 
+	        Se debe incluir en la funci&oacute;n "preUp".
+	        Borra la versi&oacute;n de la tabla de migraciones para que no tire error. 
+	        Esto se utiliza en caso de que se deba correr varias veces la misma migraci&oacute;n.
+	* setShowParameters: recibe como par&acute;metro un TRUE/FALSE.
+	        Se utiliza para mostrar los par&acute;metros de cada una de las sentencias sql.
+    * showResult: no recibe par&acute;metros.
+            Se encarga de mostrar como se ejecuto cada uno de las sentencias sql.
+    * executeYaml: recibe como par&acute;metro el nombre del archivo a incorporar. 
+            Se toma como base el directorio app/DoctrineMigrations/ 
+```
+
+```bash
+$ bin/console doctrine:migrations:execute <numberOfMigrations>
+```
+
+

+ 10 - 0
composer.json

@@ -0,0 +1,10 @@
+{
+    "name": "ik/migrations-bundle",
+    "description": "Flowdat 3 Migrations Bundle",
+    "keywords": ["Admin Generator", "admin", "migrations", "bundle"],
+    "autoload": {
+        "psr-4": { "MigrationsBundle\\": "" }
+    },
+    "version": "1.0",
+    "minimum-stability": "stable"
+}