From 9550224559ca871930f9b95c35afe1b0ae7c79e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20L=C3=A9cluse?= <laurent.lecluse@unicaen.fr> Date: Fri, 6 May 2016 13:52:47 +0000 Subject: [PATCH] transfert du code depuis OSE et adaptation --- Module.php | 2 - config/module.config.php | 164 ++++- config/unicaen-import.global.php.dist | 10 +- .../Controller/ImportController.php | 139 ++++ .../Db/Interfaces/ImportAwareInterface.php | 42 ++ src/UnicaenImport/Entity/Db/Source.php | 166 +++++ .../Entity/Db/Traits/ImportAwareTrait.php | 75 +++ .../Entity/Db/Traits/SourceAwareTrait.php | 42 ++ .../Entity/Differentiel/Ligne.php | 212 +++++++ .../Entity/Differentiel/Query.php | 552 ++++++++++++++++ src/UnicaenImport/Entity/Schema/Column.php | 64 ++ src/UnicaenImport/Exception/Exception.php | 54 ++ .../Exception/MissingDependency.php | 12 + src/UnicaenImport/Options/ModuleOptions.php | 73 +++ .../Options/ModuleOptionsFactory.php | 27 + .../Traits/ModuleOptionsAwareTrait.php | 56 ++ .../Processus/ImportProcessus.php | 80 +++ .../Traits/ImportProcessusAwareTrait.php | 56 ++ .../Provider/Privilege/Privileges.php | 20 + src/UnicaenImport/Service/AbstractService.php | 169 +++++ .../Service/DifferentielService.php | 83 +++ .../Service/QueryGeneratorService.php | 596 ++++++++++++++++++ src/UnicaenImport/Service/SchemaService.php | 146 +++++ .../Traits/DifferentielServiceAwareTrait.php | 56 ++ .../QueryGeneratorServiceAwareTrait.php | 56 ++ .../Traits/SchemaServiceAwareTrait.php | 56 ++ .../DifferentielLigne/DifferentielLigne.php | 225 +++++++ .../View/Helper/DifferentielListe.php | 122 ++++ view/unicaen-import/import/config.phtml | 3 + view/unicaen-import/import/index.phtml | 10 + view/unicaen-import/import/show-diff.phtml | 56 ++ .../import/show-import-tbl.phtml | 33 + .../import/update-materialized-view.php | 8 + .../unicaen-import/import/update-tables.phtml | 1 + .../import/update-views-and-packages.phtml | 1 + view/unicaen-import/import/update.phtml | 28 + 36 files changed, 3489 insertions(+), 6 deletions(-) create mode 100644 src/UnicaenImport/Controller/ImportController.php create mode 100644 src/UnicaenImport/Entity/Db/Interfaces/ImportAwareInterface.php create mode 100644 src/UnicaenImport/Entity/Db/Source.php create mode 100644 src/UnicaenImport/Entity/Db/Traits/ImportAwareTrait.php create mode 100644 src/UnicaenImport/Entity/Db/Traits/SourceAwareTrait.php create mode 100644 src/UnicaenImport/Entity/Differentiel/Ligne.php create mode 100644 src/UnicaenImport/Entity/Differentiel/Query.php create mode 100644 src/UnicaenImport/Entity/Schema/Column.php create mode 100644 src/UnicaenImport/Exception/Exception.php create mode 100644 src/UnicaenImport/Exception/MissingDependency.php create mode 100644 src/UnicaenImport/Options/ModuleOptions.php create mode 100644 src/UnicaenImport/Options/ModuleOptionsFactory.php create mode 100644 src/UnicaenImport/Options/Traits/ModuleOptionsAwareTrait.php create mode 100644 src/UnicaenImport/Processus/ImportProcessus.php create mode 100644 src/UnicaenImport/Processus/Traits/ImportProcessusAwareTrait.php create mode 100644 src/UnicaenImport/Provider/Privilege/Privileges.php create mode 100644 src/UnicaenImport/Service/AbstractService.php create mode 100644 src/UnicaenImport/Service/DifferentielService.php create mode 100644 src/UnicaenImport/Service/QueryGeneratorService.php create mode 100644 src/UnicaenImport/Service/SchemaService.php create mode 100644 src/UnicaenImport/Service/Traits/DifferentielServiceAwareTrait.php create mode 100644 src/UnicaenImport/Service/Traits/QueryGeneratorServiceAwareTrait.php create mode 100644 src/UnicaenImport/Service/Traits/SchemaServiceAwareTrait.php create mode 100644 src/UnicaenImport/View/Helper/DifferentielLigne/DifferentielLigne.php create mode 100644 src/UnicaenImport/View/Helper/DifferentielListe.php create mode 100644 view/unicaen-import/import/config.phtml create mode 100644 view/unicaen-import/import/index.phtml create mode 100644 view/unicaen-import/import/show-diff.phtml create mode 100644 view/unicaen-import/import/show-import-tbl.phtml create mode 100644 view/unicaen-import/import/update-materialized-view.php create mode 100644 view/unicaen-import/import/update-tables.phtml create mode 100644 view/unicaen-import/import/update-views-and-packages.phtml create mode 100644 view/unicaen-import/import/update.phtml diff --git a/Module.php b/Module.php index 4afc351..d4b4133 100644 --- a/Module.php +++ b/Module.php @@ -5,8 +5,6 @@ namespace UnicaenImport; use Zend\ModuleManager\Feature\ConfigProviderInterface; use Zend\Mvc\MvcEvent; -include_once 'Functions.php'; - /** * * diff --git a/config/module.config.php b/config/module.config.php index 8cbd0ff..83e127d 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -1,6 +1,164 @@ <?php +namespace UnicaenImport; + +use UnicaenAuth\Guard\PrivilegeController; +use UnicaenImport\Provider\Privilege\Privileges; + return [ - 'unicaen-import' => [ - ] -]; + 'controllers' => [ + 'invokables' => [ + 'Import\Controller\Import' => Controller\ImportController::class, + ], + ], + + 'router' => [ + 'routes' => [ + 'import' => [ + 'type' => 'Segment', + 'options' => [ + 'route' => '/import[/:action][/:table]', + 'defaults' => [ + '__NAMESPACE__' => 'Import\Controller', + 'controller' => 'Import', + 'action' => 'index', + 'table' => null, + ], + ], + ], + ], + ], + + 'navigation' => [ + 'default' => [ + 'home' => [ + 'pages' => [ + 'import' => [ + 'label' => 'Import', + 'order' => 1, + 'route' => 'import', + 'resource' => PrivilegeController::getResourceId('Import\Controller\Import', 'index'), + 'pages' => [ + 'showDiff' => [ + 'label' => "Écarts entre les données de l'application et ses sources", + 'description' => "Affiche, table par table, la liste des données différentes entre l'application et ses sources de données", + 'route' => 'import', + 'resource' => PrivilegeController::getResourceId('Import\Controller\Import', 'showdiff'), + 'params' => [ + 'action' => 'showDiff', + ], + ], + 'updateTables' => [ + 'label' => "Mise à jour des données à partir de leurs sources", + 'description' => "Met à jour l'ensemble des données partir de leurs sources respectives.", + 'route' => 'import', + 'resource' => PrivilegeController::getResourceId('Import\Controller\Import', 'updateTables'), + 'params' => [ + 'action' => 'updateTables', + ], + ], + 'show-import-tbl' => [ + 'label' => "Tableau de bord principal", + 'description' => "Liste, table par table, les colonnes dont les données sont importables ou non, leur caractéristiques et l'état de l'import à leur niveau.", + 'route' => 'import', + 'resource' => PrivilegeController::getResourceId('Import\Controller\Import', 'show-import-tbl'), + 'params' => [ + 'action' => 'show-import-tbl', + ], + ], + 'updateViewsAndPackages' => [ + 'label' => "Mise à jour des vues différentielles et des procédures de mise à jour", + 'description' => "Réactualise les vues différentielles d'import. Ces dernières servent à déterminer quelles données ont changé, + sont apparues ou ont disparues des sources de données. + Met également à jour les procédures de mise à jour qui actualisent les données de l'application à partir des informations + fournies par les vues différentielles. + Cette réactualisation n'est utile que si les vues sources ont été modifiées.", + 'route' => 'import', + 'resource' => PrivilegeController::getResourceId('Import\Controller\Import', 'updateViewsAndPackages'), + 'params' => [ + 'action' => 'updateViewsAndPackages', + ], + ], + ], + ], + ], + ], + ], + ], + + 'bjyauthorize' => [ + 'guards' => [ + PrivilegeController::class => [ + [ + 'controller' => 'Import\Controller\Import', + 'action' => ['index'], + 'privileges' => [Privileges::IMPORT_ECARTS, Privileges::IMPORT_MAJ, Privileges::IMPORT_TBL, Privileges::IMPORT_VUES_PROCEDURES], + ], + [ + 'controller' => 'Import\Controller\Import', + 'action' => ['showDiff'], + 'privileges' => [Privileges::IMPORT_ECARTS], + ], + [ + 'controller' => 'Import\Controller\Import', + 'action' => ['show-import-tbl'], + 'privileges' => [Privileges::IMPORT_TBL], + ], + [ + 'controller' => 'Import\Controller\Import', + 'action' => ['update', 'updateTables'], + 'privileges' => [Privileges::IMPORT_MAJ], + ], + [ + 'controller' => 'Import\Controller\Import', + 'action' => ['updateViewsAndPackages'], + 'privileges' => [Privileges::IMPORT_VUES_PROCEDURES], + ], + ], + ], + ], + + 'doctrine' => [ + 'driver' => [ + 'unicaen_import_driver' => [ + 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver', + 'cache' => 'array', + 'paths' => [ + __DIR__ . '/../src/UnicaenImport/Entity/Db', + ], + ], + 'orm_default' => [ + 'class' => 'Doctrine\ORM\Mapping\Driver\DriverChain', + 'drivers' => [ + 'UnicaenImport\Entity\Db' => 'unicaen_import_driver', + ], + ], + ], + ], + + 'service_manager' => [ + 'invokables' => [ + 'UnicaenImport\Service\Schema' => Service\SchemaService::class, + 'UnicaenImport\Service\QueryGenerator' => Service\QueryGeneratorService::class, + 'UnicaenImport\Service\Differentiel' => Service\DifferentielService::class, + 'UnicaenImport\Processus\Import' => Processus\ImportProcessus::class, + ], + 'factories' => [ + 'UnicaenImport\Options\Module' => Options\ModuleOptionsFactory::class, + ], + ], + + 'view_helpers' => [ + 'invokables' => [ + 'differentielListe' => View\Helper\DifferentielListe::class, + 'differentielLigne' => View\Helper\DifferentielLigne\DifferentielLigne::class, + ], + ], + + 'view_manager' => [ + 'template_path_stack' => [ + 'import' => __DIR__ . '/../view', + ], + ], + +]; \ No newline at end of file diff --git a/config/unicaen-import.global.php.dist b/config/unicaen-import.global.php.dist index b625128..2e85764 100644 --- a/config/unicaen-import.global.php.dist +++ b/config/unicaen-import.global.php.dist @@ -1,4 +1,12 @@ <?php return [ -]; + /* Configuration d'UnicaenImport */ + 'unicaen-import' => [ + + //Liste des aides de vues facilitant la lecture du différentiel (écarts de données entre l'appli et sa source) + 'differentiel_view_helpers' => [ + /* nom de la table (attention à la CASSE) => Nom de l'aide de vue (qui doit hériter de UnicaenImport\View\Helper\DifferentielLigne\DifferentielLigne) */ + ], + ], +]; \ No newline at end of file diff --git a/src/UnicaenImport/Controller/ImportController.php b/src/UnicaenImport/Controller/ImportController.php new file mode 100644 index 0000000..31d98c8 --- /dev/null +++ b/src/UnicaenImport/Controller/ImportController.php @@ -0,0 +1,139 @@ +<?php +namespace UnicaenImport\Controller; + +use UnicaenImport\Processus\Traits\ImportProcessusAwareTrait; +use UnicaenImport\Service\Traits\DifferentielServiceAwareTrait; +use UnicaenImport\Service\Traits\QueryGeneratorServiceAwareTrait; +use UnicaenImport\Service\Traits\SchemaServiceAwareTrait; +use Zend\Mvc\Controller\AbstractActionController; +use UnicaenImport\Entity\Differentiel\Query; + +/** + * + * + * @author Laurent Lécluse <laurent.lecluse at unicaen.fr> + */ +class ImportController extends AbstractActionController +{ + use SchemaServiceAwareTrait; + use QueryGeneratorServiceAwareTrait; + use DifferentielServiceAwareTrait; + use ImportProcessusAwareTrait; + + + + + public function indexAction() + { + } + + + + public function updateViewsAndPackagesAction() + { + try { + $this->getProcessusImport()->updateViewsAndPackages(); + $message = 'Mise à jour des vues différentielles et du paquetage d\'import terminés'; + } catch (\Exception $e) { + $message = 'Une erreur a été rencontrée.'; + throw new \UnicaenApp\Exception\LogicException("import impossible", null, $e); + } + $title = "Résultat"; + + return compact('message', 'title'); + } + + + + public function showImportTblAction() + { + $data = $this->getServiceSchema()->getSchema(); + + return compact('data'); + } + + + + public function showDiffAction() + { + $tableName = $this->params()->fromRoute('table'); + + $sc = $this->getServiceSchema(); + + $mviews = $sc->getImportMviews(); + + if ($tableName) { + $tables = [$tableName]; + } else { + $tables = $sc->getImportTables(); + sort($tables); + } + + $data = []; + foreach ($tables as $table) { + $query = new Query($table); + $query->setLimit(101); + $data[$table] = $this->getServiceDifferentiel()->make($query)->fetchAll(); + } + + return compact('data', 'mviews'); + } + + + + public function updateAction() + { + $errors = []; + $tableName = $this->params()->fromRoute('table'); + $typeMaj = $this->params()->fromPost('type-maj'); + + $query = new Query($tableName); + + $sq = $this->getServiceQueryGenerator(); + + /* Mise à jour des données et récupération des éventuelles erreurs */ + try { + if ('vue-materialisee' == $typeMaj) { + $sq->execMajVM($tableName); + } else { + $errors = $errors + $sq->syncTable($tableName); + //$sq->execMaj($query); + } + } catch (\Exception $e) { + $errors = [$e->getMessage()]; + } + $query->setNotNull([]); // Aucune colonne ne doit être non nulle !! + $query->setLimit(101); + $lignes = $this->getServiceDifferentiel()->make($query)->fetchAll(); + + return [ + 'lignes' => $lignes, + 'table' => $tableName, + 'errors' => $errors, + ]; + } + + + + public function updateTablesAction() + { + $tables = $this->getServiceSchema()->getImportTables(); + sort($tables); + + $message = ''; + try { + foreach ($tables as $table) { + $message .= '<div>Table "' . $table . '" Mise à jour.</div>'; + $this->getServiceQueryGenerator()->execMaj(new Query($table)); + } + $message .= 'Mise à jour des données terminée'; + } catch (\Exception $e) { + $message = 'Une erreur a été rencontrée.'; + throw new \UnicaenApp\Exception\LogicException("mise à jour des données impossible", null, $e); + } + + $title = "Résultat"; + + return compact('message', 'title'); + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Db/Interfaces/ImportAwareInterface.php b/src/UnicaenImport/Entity/Db/Interfaces/ImportAwareInterface.php new file mode 100644 index 0000000..d58a308 --- /dev/null +++ b/src/UnicaenImport/Entity/Db/Interfaces/ImportAwareInterface.php @@ -0,0 +1,42 @@ +<?php + +namespace UnicaenImport\Entity\Db\Interfaces; +use UnicaenImport\Entity\Db\Source; + + +/** + * Interface des entités possédant une gestion de l'import + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +interface ImportAwareInterface +{ + public function setSource(Source $source = null); + + + + /** + * @return Source + */ + public function getSource(); + + + + /** + * Set sourceCode + * + * @param string $sourceCode + * + * @return self + */ + public function setSourceCode($sourceCode); + + + + /** + * Get sourceCode + * + * @return string + */ + public function getSourceCode(); +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Db/Source.php b/src/UnicaenImport/Entity/Db/Source.php new file mode 100644 index 0000000..be64a52 --- /dev/null +++ b/src/UnicaenImport/Entity/Db/Source.php @@ -0,0 +1,166 @@ +<?php + +namespace UnicaenImport\Entity\Db; + +use Doctrine\ORM\Mapping as ORM; + +/** + * Source + * + * @ORM\Entity + * @ORM\Table(name="SOURCE") + */ +class Source +{ + + /** + * @var string + * @ORM\Column(name="CODE", type="string", length=255, unique=true, nullable=false) + */ + protected $code; + + /** + * @var boolean + * @ORM\Column(name="IMPORTABLE", type="boolean", nullable=false) + */ + protected $importable; + + /** + * @var string + * @ORM\Column(name="LIBELLE", type="string", length=255, unique=true, nullable=false) + */ + protected $libelle; + + /** + * @var int + * @ORM\Id + * @ORM\Column(name="ID", type="integer") + * @ORM\GeneratedValue(strategy="SEQUENCE") + */ + protected $id; + + + + /** + * Set code + * + * @param string $code + * + * @return Source + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + + + /** + * Get code + * + * @return string + */ + public function getCode() + { + return $this->code; + } + + + + /** + * Set importable + * + * @param boolean $importable + * + * @return Source + */ + public function setImportable($importable) + { + $this->importable = $importable; + + return $this; + } + + + + /** + * Get importable + * + * @return boolean + */ + public function getImportable() + { + return $this->importable; + } + + + + /** + * Set libelle + * + * @param string $libelle + * + * @return Source + */ + public function setLibelle($libelle) + { + $this->libelle = $libelle; + + return $this; + } + + + + /** + * Get libelle + * + * @return string + */ + public function getLibelle() + { + return $this->libelle; + } + + + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + + + /** + * Retourne la représentation littérale de cet objet. + * + * @return string + */ + public function __toString() + { + return $this->getLibelle(); + } + + + + /** + * @since PHP 5.6.0 + * This method is called by var_dump() when dumping an object to get the properties that should be shown. + * If the method isn't defined on an object, then all public, protected and private properties will be shown. + * + * @return array + * @link http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo + */ + function __debugInfo() + { + return [ + 'libelle' => $this->libelle, + ]; + } +} diff --git a/src/UnicaenImport/Entity/Db/Traits/ImportAwareTrait.php b/src/UnicaenImport/Entity/Db/Traits/ImportAwareTrait.php new file mode 100644 index 0000000..9477747 --- /dev/null +++ b/src/UnicaenImport/Entity/Db/Traits/ImportAwareTrait.php @@ -0,0 +1,75 @@ +<?php + +namespace UnicaenImport\Entity\Db\Traits; + +use UnicaenImport\Entity\Db\Source; + +/** + * Description of SourceAwareTrait + * + * @author UnicaenCode + */ +trait ImportAwareTrait +{ + /** + * @var Source + */ + private $source; + + /** + * @var string + */ + private $sourceCode; + + + + /** + * @param Source $source + * + * @return self + */ + public function setSource(Source $source = null) + { + $this->source = $source; + + return $this; + } + + + + /** + * @return Source + */ + public function getSource() + { + return $this->source; + } + + + + /** + * Set sourceCode + * + * @param string $sourceCode + * + * @return self + */ + public function setSourceCode($sourceCode) + { + $this->sourceCode = $sourceCode; + + return $this; + } + + + + /** + * Get sourceCode + * + * @return string + */ + public function getSourceCode() + { + return $this->sourceCode; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Db/Traits/SourceAwareTrait.php b/src/UnicaenImport/Entity/Db/Traits/SourceAwareTrait.php new file mode 100644 index 0000000..f4cd454 --- /dev/null +++ b/src/UnicaenImport/Entity/Db/Traits/SourceAwareTrait.php @@ -0,0 +1,42 @@ +<?php + +namespace UnicaenImport\Entity\Db\Traits; + +use UnicaenImport\Entity\Db\Source; + +/** + * Description of SourceAwareTrait + * + * @author UnicaenCode + */ +trait SourceAwareTrait +{ + /** + * @var Source + */ + private $source; + + + + + + /** + * @param Source $source + * @return self + */ + public function setSource( Source $source = null ) + { + $this->source = $source; + return $this; + } + + + + /** + * @return Source + */ + public function getSource() + { + return $this->source; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Differentiel/Ligne.php b/src/UnicaenImport/Entity/Differentiel/Ligne.php new file mode 100644 index 0000000..a41d55c --- /dev/null +++ b/src/UnicaenImport/Entity/Differentiel/Ligne.php @@ -0,0 +1,212 @@ +<?php + +namespace UnicaenImport\Entity\Differentiel; + +use Doctrine\ORM\EntityManager; +use UnicaenImport\Entity\Db\Source; + +/** + * Classe permettant de récupérer une ligne de différentiel + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class Ligne +{ + + /** + * Entity Manager + * + * @var EntityManager + */ + protected $entityManager; + + /** + * Nom de la table + * + * @var string + */ + protected $tableName; + + /** + * ID + * + * @var integer + */ + protected $id; + + /** + * Action + * + * @var string + */ + protected $action; + + /** + * ID de la source + * + * @var Source + */ + protected $source; + + /** + * Code source + * + * @var string + */ + protected $sourceCode; + + /** + * Données des colonnes + * + * @var array + */ + protected $values; + + /** + * Liste des colonnes ayant changé + * + * @var boolean[] + */ + protected $changed; + + + + + /** + * + * @param Statement $stmt + */ + public function __construct(EntityManager $entityManager, $tableName, array $data) + { + $this->tableName = $tableName; + $this->entityManager = $entityManager; + + $this->id = (integer)$data['ID']; + unset($data['ID']); + + $this->action = $data['IMPORT_ACTION']; + unset($data['IMPORT_ACTION']); + + $this->source = $entityManager->find(\UnicaenImport\Entity\Db\Source::class, (integer)$data['SOURCE_ID']); + unset($data['SOURCE_ID']); + + $this->sourceCode = $data['SOURCE_CODE']; + unset($data['SOURCE_CODE']); + + $keys = array_keys( $data ); + foreach( $keys as $key ){ + if (in_array('U_'.$key, $keys)){ + $this->values[$key] = $data[$key]; + $this->changed[$key] = $data['U_'.$key] === '1'; + } + } + } + + /** + * Retourne le nom de la table correspondante + * + * @return string + */ + public function getTableName() + { + return $this->tableName; + } + + /** + * Retourne l'ID OSE de l'enregistrement + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Retourne le type d'action prévue pour l'import + * + * @return string + */ + public function getAction() + { + return $this->action; + } + + /** + * Retourne lasource de données + * + * @return Source + */ + public function getSource() + { + return $this->source; + } + + /** + * Retourne le code de la donnée source + * + * @return string + */ + public function getSourceCode() + { + return $this->sourceCode; + } + + /** + * Retourne, sous forme de chaîne de caractères, la valeur de la colonne donnée + * + * @param string $colName + * @return string + */ + public function get( $colName ) + { + return $this->values[$colName]; + } + + /** + * Retourne l'entité Doctrine correspondante + * + * @return StdClass + */ + public function getEntity() + { + $filter = new \Zend\Filter\Word\UnderscoreToCamelCase; + $entityClass = 'Application\\Entity\Db\\'.$filter->filter(strtolower($this->getTableName())); + return $this->entityManager->find($entityClass, $this->getId()); + } + + /** + * Retourne true si la colonne $colName a changé, false sinon + * + * @param string $colName + * @return boolean + */ + public function hasChanged( $colName ) + { + return $this->changed[$colName]; + } + + /** + * Retourne un tableau des colonnes ayant changé + * + * @return array + */ + public function getChanges() + { + $changes = []; + foreach( $this->changed as $colName => $changed ){ + if ($changed) $changes[$colName] = $this->values[$colName]; + } + return $changes; + } + + /** + * Retourne le gestionnaire d'entités correspondant + * + * @return EntityManager + */ + public function getEntityManager() + { + return $this->entityManager; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Differentiel/Query.php b/src/UnicaenImport/Entity/Differentiel/Query.php new file mode 100644 index 0000000..32a043a --- /dev/null +++ b/src/UnicaenImport/Entity/Differentiel/Query.php @@ -0,0 +1,552 @@ +<?php + +namespace UnicaenImport\Entity\Differentiel; + +use UnicaenImport\Entity\Db\Source; +use UnicaenImport\Exception\Exception; +use UnicaenImport\Service\QueryGeneratorService; +use UnicaenImport\Service\AbstractService; + + +/** + * Classe permettant de créer une requête de récupération de différentiel + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class Query +{ + + const ACTION_INSERT = 'insert'; + const ACTION_UPDATE = 'update'; + const ACTION_DELETE = 'delete'; + const ACTION_UNDELETE = 'undelete'; + + /** + * Nom de la table + * + * @var string + */ + protected $tableName; + + /** + * ID + * + * @var integer|integer[]|null + */ + protected $id; + + /** + * Action + * + * @var string|string[]|null + */ + protected $action; + + /** + * Source de données + * + * @var Source|Source[]|null + */ + protected $source; + + /** + * Code source + * + * @var string|string[]|null + */ + protected $sourceCode; + + /** + * inTable + * + * @var string + */ + protected $inTable; + + /** + * Liste des colonnes ayant changé à filtrer + * + * @var string|string[]|null + */ + protected $colChanged; + + /** + * Liste des colonnes avec des valeurs spéciales à filtrer + * + * @var array + */ + protected $colValues = []; + + /** + * Liste des colonnes ne devant pas être nulles + * + * @var string[] + */ + protected $notNull = []; + + /** + * Limite au nombre d'enregistrements retournés + * + * @var integer + */ + protected $limit; + + /** + * ignoreFields + * + * @var string[] + */ + protected $ignoreFields; + + /** + * defaultSqlCriterion + * + * @var string + */ + protected $defaultSqlCriterion; + + + + + + /** + * Constructeur + * + * @param string $tableName + */ + function __construct( $tableName ) + { + $this->setTableName($tableName); + } + + /** + * + * @param QueryGeneratorService $queryGenerator + * @return self + */ + public function addDefaultSqlCriterion( QueryGeneratorService $queryGenerator ) + { + if ($this->getTableName()){ + $this->defaultSqlCriterion = $queryGenerator->getSqlCriterion($this->getTableName()); + } + return $this; + } + + /** + * Retourne le nom de la table correspondante + * + * @return string + */ + public function getTableName() + { + return $this->tableName; + } + + /** + * + * @param string $tableName + * @return self + */ + public function setTableName($tableName) + { + $this->tableName = (string)$tableName; + return $this; + } + + /** + * Retourne le ou les ID scrutés + * + * @return integer|integer[]|null + */ + public function getId() + { + return $this->id; + } + + /** + * Ajoute un ou plusieurs ID + * + * @param integer|integer[]|null $id + * @return self + */ + public function setId($id) + { + if (empty($id)){ + $this->id = null; + }elseif( is_array($id)){ + $this->id = []; + foreach( $id as $i ) $this->id[] = (int)$i; + }else{ + $this->id = (int)$id; + } + return $this; + } + + /** + * + * Retourne la ou les actions scrutées + * + * @return string|string[]|null + */ + public function getAction() + { + return $this->action; + } + + /** + * Ajoute une ou plusieures actions + * + * @param string|string[]|null $action + * @return self + */ + public function setAction($action) + { + $goodActions = [self::ACTION_DELETE,self::ACTION_INSERT,self::ACTION_UNDELETE,self::ACTION_UPDATE]; + + if (empty($action)){ + $this->action = null; + }elseif( is_array($action)){ + foreach( $action as $a ){ + if (! in_array($a,$goodActions)){ + throw new Exception('Requête erronée : action "'.$a.'" invalide'); + } + } + $this->action = $action; + }else{ + if (! in_array($action,$goodActions)){ + throw new Exception('Requête erronée : action "'.$action.'" invalide'); + } + $this->action = $action; + } + return $this; + } + + /** + * Retourne la ou les sources de données + * + * @return Source|Source[]|null + */ + public function getSource() + { + return $this->source; + } + + /** + * Ajoute un ou plusieurs sources de données + * + * @param Source|Source[]|null $source + * @return self + */ + public function setSource($source) + { + if (empty($source)){ + $this->source = null; + }elseif( is_array($source)){ + foreach( $source as $s ){ + if (! $s instanceof Source){ + throw new Exception('Requête erronée : classe source "'.get_class($s).'" invalide'); + } + if (! $s->getImportable()){ + throw new Exception('Requête erronée : source "'.$s->getLibelle().'" non importable'); + } + } + $this->source = $source; + }else{ + if (! $source instanceof Source){ + throw new Exception('Requête erronée : classe source "'.get_class($source).'" invalide'); + } + if (! $source->getImportable()){ + throw new Exception('Requête erronée : source "'.$source->getLibelle().'" non importable'); + } + $this->source = $source; + } + return $this; + } + + /** + * Ajoute un ou plusieurs enregistrements sources + * + * @return string|string[]|null + */ + public function getSourceCode() + { + return $this->sourceCode; + } + + /** + * + * @param string|string[]|null $sourceCode + * @return self + */ + public function setSourceCode($sourceCode) + { + if (empty($sourceCode)){ + $this->sourceCode = null; + }elseif( is_array($sourceCode)){ + $this->sourceCode = []; + foreach( $sourceCode as $sc ) $this->sourceCode[] = (string)$sc; + }else{ + $this->sourceCode = (string)$sourceCode; + } + return $this; + } + + /** + * Retourne la table pour laquelle l'enregistrement doit ou peut être présent + * + * @return string + */ + public function getInTable() + { + return $this->inTable; + } + + /** + * Détermine si l'enregistrement doit ou peut être présent dans la table nommée ou non + * + * @param string $inTable + * @return self + */ + public function setInTable($inTable) + { + $this->inTable = $inTable; + return $this; + } + + + /** + * Retourne la liste des colonnes scrutées + * + * @return string|string[]|null + */ + public function getColChanged() + { + return $this->colChanged; + } + + /** + * Ajoute une ou plusieurs colonnes + * + * @param string|string[]|null $colChanged + * @return self + */ + public function setColChanged($colChanged) + { + if (empty($colChanged)){ + $this->colChanged = null; + }elseif( is_array($colChanged)){ + $this->colChanged = []; + foreach( $colChanged as $c ) $this->colChanged[] = (string)$c; + }else{ + $this->colChanged = (string)$colChanged; + } + return $this; + } + + /** + * Retourne le liste des valeurs à filtrer, colonne par colonne + * + * @return array + */ + public function getColValues() + { + return $this->colValues; + } + + /** + * Applique une liste de colonnes à scruter en fonction des valeurs transmises + * + * format du tableau : {Nom de colonne => Valeur(s) à scruter} + * + * @param array $colValues + * @return self + */ + public function setColValues( array $colValues ) + { + $this->colValues = $colValues; + } + + /** + * Détermine une valeu à scruter pour une colonne donnée + * + * @param string $column + * @param mixed $value + */ + public function addColValue( $column, $value ) + { + $this->colValues[$column] = $value; + } + + /** + * Retourne la liste des colonnes ne devant pas être nulles + * + * @return string[] + */ + public function getNotNull() + { + return $this->notNull; + } + + /** + * Applique une liste de colonnes ne devant pas être nulles + * + * + * @param string[] $notNull + * @return self + */ + public function setNotNull( array $notNull ) + { + $this->notNull = $notNull; + } + + /** + * Ajoute une colonne ne devant pas être nulle + * + * @param string $column + */ + public function addNotNull( $column ) + { + $this->notNull[] = $column; + return $this; + } + + /** + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * + * @param integer $limit + * @return self + */ + public function setLimit($limit) + { + $this->limit = (int)$limit; + return $this; + } + + /** + * Retourne la liste des champs à ignorer pour la MAJ + * + * @return string[] + */ + public function getIgnoreFields() + { + return $this->ignoreFields; + } + + /** + * Modifie la liste des champs à ignorer pour la MAJ + * + * @param string[] $ignoreFields + * @return self + */ + public function setIgnoreFields(array $ignoreFields) + { + $this->ignoreFields = $ignoreFields; + return $this; + } + + /** + * Ajoute un champ à la liste des champs à ignorer pour la MAJ + * + * @param string $ignoreField + * @return self + */ + public function addIgnoreField($ignoreField) + { + if (! is_array($this->ignoreFields)) $this->ignoreFields = []; + if (! in_array($ignoreField, $this->ignoreFields)){ + $this->ignoreFields[] = $ignoreField; + } + return $this; + } + + /** + * Construit la requête SQL correspondante + * + * @return string + */ + public function toSql($full=true) + { + $viewName = AbstractService::escapeKW('V_DIFF_'.$this->tableName); + + $where = []; + if (! empty($this->id)){ + $where[] = $viewName.'.ID'.AbstractService::equals($this->id); + } + + if (! empty($this->action)){ + $w = $viewName.'.IMPORT_ACTION'.AbstractService::equals($this->action); + if (! empty($this->inTable)){ + $w = '('.$w.' OR '.$viewName.'.SOURCE_CODE IN (SELECT SOURCE_CODE FROM '.AbstractService::escapeKW($this->inTable).')'.')'; + } + $where[] = $w; + } + + if (! empty($this->source)){ + if (is_array($this->source)){ + $values = []; + foreach( $this->source as $value ){ $values[] = $value->getId(); } + $where[] = $viewName.'.SOURCE_ID'.AbstractService::equals($values); + }else{ + $where[] = $viewName.'.SOURCE_ID'.AbstractService::equals($this->source->getId()); + } + } + + if (! empty($this->sourceCode)){ + $where[] = $viewName.'.SOURCE_CODE'.AbstractService::equals($this->sourceCode); + } + + if (! empty($this->colChanged)){ + $cols = (array)$this->colChanged; + $cond = []; + foreach( $cols as $column ){ + $cond[] = $viewName.'.'.AbstractService::escapeKW ('U_'.$column).' = 1'; + } + $where[] = '('.implode( ' OR ', $cond).')'; + } + + if (! empty($this->colValues)){ + foreach( $this->colValues as $column => $value ){ + $where[] = $viewName.'.'.AbstractService::escapeKW($column).AbstractService::equals($value); + } + } + + if (! empty($this->notNull)){ + foreach( $this->notNull as $column ){ + $where[] = $viewName.'.'.AbstractService::escapeKW($column).' IS NOT NULL'; + } + } + + if ($this->limit !== null){ + $where[] = 'ROWNUM <= '.$this->limit; + } + + + if ($full){ + $sql = 'SELECT * FROM '.$viewName.' '; + }else{ + $sql = ''; + } + if (! empty($where)){ + if (! empty($this->defaultSqlCriterion)){ + $sql .= $this->defaultSqlCriterion.' AND '.implode( ' AND ', $where ); + }else{ + $sql .= 'WHERE '.implode( ' AND ', $where ); + } + }else{ + if (! empty($this->defaultSqlCriterion)){ + $sql .= $this->defaultSqlCriterion; + } + } + + return $sql; + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/Entity/Schema/Column.php b/src/UnicaenImport/Entity/Schema/Column.php new file mode 100644 index 0000000..7b59c3c --- /dev/null +++ b/src/UnicaenImport/Entity/Schema/Column.php @@ -0,0 +1,64 @@ +<?php + +namespace UnicaenImport\Entity\Schema; + + + +/** + * + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class Column +{ + + /** + * Type de données + * + * @var string + */ + public $dataType; + + /** + * Longueur + * + * @var integer + */ + public $length; + + /** + * Nullable + * + * @var boolean + */ + public $nullable; + + /** + * Si la colonne possède ou non une valeur par défaut + * + * @var boolean + */ + public $hasDefault; + + /** + * Nom de la table référence (si clé étrangère) + * + * @var string + */ + public $refTableName; + + /** + * Nom du champ référence (si clé étrangère) + * + * @var string + */ + public $refColumnName; + + /** + * Si l'import par synchronisation est actif ou non + * + * @var boolean + */ + public $importActif; + +} \ No newline at end of file diff --git a/src/UnicaenImport/Exception/Exception.php b/src/UnicaenImport/Exception/Exception.php new file mode 100644 index 0000000..6fec024 --- /dev/null +++ b/src/UnicaenImport/Exception/Exception.php @@ -0,0 +1,54 @@ +<?php + +namespace UnicaenImport\Exception; + +use RuntimeException; + +/** + * + * + * @author Laurent Lécluse <laurent.lecluse at unicaen.fr> + */ +class Exception extends RuntimeException { + + /** + * @param \Exception $exception + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function duringMajMVException(\Exception $exception, $tableName) + { + if (! $exception->getPrevious() instanceof \Doctrine\DBAL\Driver\OCI8\OCI8Exception){ + // Non gérée + return $exception; + } + + $msg = $exception->getPrevious()->getMessage(); + + $msg = "Erreur lors de la mise à jour de la vue métarialisée liée à la table $tableName\n\n$msg"; + + return new self($msg, 0, $exception); + } + + /** + * @param \Exception $exception + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function duringMajException(\Exception $exception, $tableName) + { + if (! $exception->getPrevious() instanceof \Doctrine\DBAL\Driver\OCI8\OCI8Exception){ + // Non gérée + return $exception; + } + + $msg = $exception->getPrevious()->getMessage(); + + $msg = "Erreur lors d'une mise à jour de données dans la table $tableName\n\n$msg"; + + return new self($msg, 0, $exception); + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/Exception/MissingDependency.php b/src/UnicaenImport/Exception/MissingDependency.php new file mode 100644 index 0000000..a078c82 --- /dev/null +++ b/src/UnicaenImport/Exception/MissingDependency.php @@ -0,0 +1,12 @@ +<?php + +namespace UnicaenImport\Exception; + +/** + * + * + * @author Laurent Lécluse <laurent.lecluse at unicaen.fr> + */ +class MissingDependency extends Exception { + +} \ No newline at end of file diff --git a/src/UnicaenImport/Options/ModuleOptions.php b/src/UnicaenImport/Options/ModuleOptions.php new file mode 100644 index 0000000..cb50bed --- /dev/null +++ b/src/UnicaenImport/Options/ModuleOptions.php @@ -0,0 +1,73 @@ +<?php + +namespace UnicaenImport\Options; + +use Zend\Stdlib\AbstractOptions; +use Zend\Stdlib\ArrayUtils; + +class ModuleOptions extends AbstractOptions +{ + /** + * Turn off strict options mode + */ + protected $__strictMode__ = false; + + /** + * @var string + */ + protected $package = 'UNICAEN_IMPORT'; + + /** + * @var array + */ + protected $differentielViewHelpers = []; + + + + /** + * @return string + */ + public function getPackage() + { + return $this->package; + } + + + + /** + * @param string $package + * + * @return ModuleOptions + */ + public function setPackage($package) + { + $this->package = $package; + + return $this; + } + + + + /** + * @return array + */ + public function getDifferentielViewHelpers() + { + return $this->differentielViewHelpers; + } + + + + /** + * @param array $differentielViewHelpers + * + * @return ModuleOptions + */ + public function setDifferentielViewHelpers($differentielViewHelpers) + { + $this->differentielViewHelpers = $differentielViewHelpers; + + return $this; + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/Options/ModuleOptionsFactory.php b/src/UnicaenImport/Options/ModuleOptionsFactory.php new file mode 100644 index 0000000..72bce9e --- /dev/null +++ b/src/UnicaenImport/Options/ModuleOptionsFactory.php @@ -0,0 +1,27 @@ +<?php + +namespace UnicaenImport\Options; + +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +/** + * Description of ModuleOptionsFactory + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class ModuleOptionsFactory implements FactoryInterface +{ + /** + * Create service + * + * @param ServiceLocatorInterface $serviceLocator + * @return mixed + */ + public function createService(ServiceLocatorInterface $serviceLocator) + { + $config = $serviceLocator->get('Configuration'); + + return new ModuleOptions(isset($config['unicaen-import']) ? $config['unicaen-import'] : []); + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Options/Traits/ModuleOptionsAwareTrait.php b/src/UnicaenImport/Options/Traits/ModuleOptionsAwareTrait.php new file mode 100644 index 0000000..72a6c3b --- /dev/null +++ b/src/UnicaenImport/Options/Traits/ModuleOptionsAwareTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace UnicaenImport\Options\Traits; + +use UnicaenImport\Options\ModuleOptions; +use RuntimeException; + +/** + * Description of ModuleOptionsAwareTrait + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +trait ModuleOptionsAwareTrait +{ + /** + * @var ModuleOptions + */ + private $optionsModule; + + + + /** + * @param ModuleOptions $optionsModule + * + * @return self + */ + public function setOptionsModule(ModuleOptions $optionsModule) + { + $this->optionsModule = $optionsModule; + + return $this; + } + + + + /** + * @return ModuleOptions + * @throws RuntimeException + */ + public function getOptionsModule() + { + if (empty($this->optionsModule)) { + if (!method_exists($this, 'getServiceLocator')) { + throw new RuntimeException('La classe ' . get_class($this) . ' n\'a pas accès au ServiceLocator.'); + } + + $serviceLocator = $this->getServiceLocator(); + if (method_exists($serviceLocator, 'getServiceLocator')) { + $serviceLocator = $serviceLocator->getServiceLocator(); + } + $this->optionsModule = $serviceLocator->get('UnicaenImport\Options\Module'); + } + + return $this->optionsModule; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Processus/ImportProcessus.php b/src/UnicaenImport/Processus/ImportProcessus.php new file mode 100644 index 0000000..15f70c7 --- /dev/null +++ b/src/UnicaenImport/Processus/ImportProcessus.php @@ -0,0 +1,80 @@ +<?php + +namespace UnicaenImport\Processus; + +use UnicaenImport\Entity\Differentiel\Query; +use UnicaenImport\Service\Traits\QueryGeneratorServiceAwareTrait; +use Zend\ServiceManager\ServiceLocatorAwareInterface; +use Zend\ServiceManager\ServiceLocatorAwareTrait; + + +/** + * + * + * @author Laurent Lécluse <laurent.lecluse at unicaen.fr> + */ +class ImportProcessus implements ServiceLocatorAwareInterface +{ + use ServiceLocatorAwareTrait; + use QueryGeneratorServiceAwareTrait; + + + /** + * Mise à jour de l'existant uniquement + */ + const A_UPDATE = 'update'; + + /** + * Insertion de nouvelles données ou restauration d'anciennes uniquement + */ + const A_INSERT = 'insert'; + + /** + * Mise à jour globale + */ + const A_ALL = 'all'; + + + + /** + * Mise à jour des vues différentielles et des paquetages de mise à jour des données + * + * @return self + */ + public function updateViewsAndPackages() + { + $this->getServiceQueryGenerator()->updateViewsAndPackages(); + return $this; + } + + + + /** + * Construit et exécute la reqûete d'interrogation des vues différentielles + * + * @param string $tableName Nom de la table + * @param string $name Nom du champ à tester + * @param string|null $value Valeur de test du champ + * @param string $action Action + * @retun self + */ + public function execMaj( $tableName, $name, $value=null, $action=self::A_ALL ) + { + if ('SOURCE_CODE' == $name && $value !== null){ + $value = (string)$value; + } + $query = new Query($tableName); + if (null !== $value) $query->addColValue($name, $value); + switch( $action ){ + case 'insert': + $query->setAction ([Query::ACTION_INSERT,Query::ACTION_UNDELETE]); + break; + case 'update': + $query->setAction ([Query::ACTION_UPDATE,Query::ACTION_DELETE]); + break; + } + $this->getServiceQueryGenerator()->execMaj($query); + return $this; + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/Processus/Traits/ImportProcessusAwareTrait.php b/src/UnicaenImport/Processus/Traits/ImportProcessusAwareTrait.php new file mode 100644 index 0000000..57aaa07 --- /dev/null +++ b/src/UnicaenImport/Processus/Traits/ImportProcessusAwareTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace UnicaenImport\Processus\Traits; + +use UnicaenImport\Processus\ImportProcessus; +use RuntimeException; + +/** + * Description of ImportProcessusAwareTrait + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +trait ImportProcessusAwareTrait +{ + /** + * @var ImportProcessus + */ + private $processusImport; + + + + /** + * @param ImportProcessus $processusImport + * + * @return self + */ + public function setProcessusImport(ImportProcessus $processusImport) + { + $this->processusImport = $processusImport; + + return $this; + } + + + + /** + * @return ImportProcessus + * @throws RuntimeException + */ + public function getProcessusImport() + { + if (empty($this->processusImport)) { + if (!method_exists($this, 'getServiceLocator')) { + throw new RuntimeException('La classe ' . get_class($this) . ' n\'a pas accès au ServiceLocator.'); + } + + $serviceLocator = $this->getServiceLocator(); + if (method_exists($serviceLocator, 'getServiceLocator')) { + $serviceLocator = $serviceLocator->getServiceLocator(); + } + $this->processusImport = $serviceLocator->get('UnicaenImport\Processus\Import'); + } + + return $this->processusImport; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Provider/Privilege/Privileges.php b/src/UnicaenImport/Provider/Privilege/Privileges.php new file mode 100644 index 0000000..15d943b --- /dev/null +++ b/src/UnicaenImport/Provider/Privilege/Privileges.php @@ -0,0 +1,20 @@ +<?php + +namespace UnicaenImport\Provider\Privilege; + +/** + * Description of Privileges + * + * Liste des privilèges utilisables dans votre application + * + * @author UnicaenCode + */ +class Privileges extends \UnicaenAuth\Provider\Privilege\Privileges +{ + + const IMPORT_ECARTS = 'import-ecarts'; + const IMPORT_MAJ = 'import-maj'; + const IMPORT_TBL = 'import-tbl'; + const IMPORT_VUES_PROCEDURES = 'import-vues-procedures'; + +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/AbstractService.php b/src/UnicaenImport/Service/AbstractService.php new file mode 100644 index 0000000..50c13ac --- /dev/null +++ b/src/UnicaenImport/Service/AbstractService.php @@ -0,0 +1,169 @@ +<?php + +namespace UnicaenImport\Service; + +use Doctrine\ORM\EntityManager; +use UnicaenApp\Service\EntityManagerAwareInterface; +use UnicaenApp\Service\EntityManagerAwareTrait; +use Zend\ServiceManager\ServiceLocatorAwareInterface; +use Zend\ServiceManager\ServiceLocatorAwareTrait; +use Zend\ServiceManager\ServiceManager; +use Zend\ServiceManager\ServiceManagerAwareInterface; +use UnicaenImport\Exception\Exception; +use ZfcUser\Entity\UserInterface; +use UnicaenAuth\Service\DbUserAwareInterface; +use Application\Entity\Db\Utilisateur; + +/** + * Classe mère des services + * + * @author Laurent Lécluse <laurent.lecluse at unicaen.fr> + */ +class AbstractService implements ServiceLocatorAwareInterface, EntityManagerAwareInterface, DbUserAwareInterface +{ + use ServiceLocatorAwareTrait; + use EntityManagerAwareTrait; + + /** + * utilisateur courant + * + * @var UserInterface + */ + protected $currentUser; + + + + + /** + * Echappe une chaîne de caractères pour convertir en SQL + * + * @param string $string + * + * @return string + */ + public static function escapeKW($string) + { + return '"' . str_replace('"', '""', strtoupper($string)) . '"'; + } + + + + /** + * Echappe une valeur pour convertir en SQL + * + * @param mixed $value + * + * @return string + */ + public static function escape($value) + { + if (null === $value) return 'NULL'; + switch (gettype($value)) { + case 'string': + return "'" . str_replace("'", "''", $value) . "'"; + case 'integer': + return (string)$value; + case 'boolean': + return $value ? '1' : '0'; + case 'double': + return (string)$value; + case 'array': + return '(' . implode(',', array_map('Import\Service\Service::escape', $value)) . ')'; + } + throw new Exception('La valeur transmise ne peut pas être convertie en SQL'); + } + + + + /** + * Retourne le code SQL correspondant à la valeur transmise, précédé de "=", "IS" ou "IN" suivant le contexte. + * + * @param mixed $value + * + * @return string + */ + public static function equals($value) + { + if (null === $value) { + $eq = ' IS '; + } elseif (is_array($value)) $eq = ' IN '; + else $eq = ' = '; + + return $eq . self::escape($value); + } + + + + /** + * Retourne une tableau des résultats de la requête transmise. + * + * + * @param string $sql + * @param array $params + * @param string $colRes + * + * @return array + */ + protected function query($sql, $params = null, $colRes = null) + { + $stmt = $this->getEntityManager()->getConnection()->executeQuery($sql, $params); + $result = []; + while ($r = $stmt->fetch()) { + if (empty($colRes)) $result[] = $r; else $result[] = $r[$colRes]; + } + + return $result; + } + + + + /** + * exécute un ordre SQL + * + * @param string $sql + * + * @return integer + */ + protected function exec($sql) + { + return $this->getEntityManager()->getConnection()->exec($sql); + } + + + + /** + * + * @return UserInterface + */ + public function getDbUser() + { + if (null === $this->currentUser) { + $this->currentUser = $this->getAppDbUser(); + } + + return $this->currentUser; + } + + + + /** + * Set Current User + * + * @param UserInterface $currentUser + */ + public function setDbUser(UserInterface $currentUser) + { + $this->currentUser = $currentUser; + } + + + + /** + * + * @return UserInterface + */ + public function getAppDbUser() + { + return $this->getEntityManager()->find(\Application\Entity\Db\Utilisateur::class, Utilisateur::APP_UTILISATEUR_ID); + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/DifferentielService.php b/src/UnicaenImport/Service/DifferentielService.php new file mode 100644 index 0000000..fb5d735 --- /dev/null +++ b/src/UnicaenImport/Service/DifferentielService.php @@ -0,0 +1,83 @@ +<?php + +namespace UnicaenImport\Service; + +use Doctrine\DBAL\Driver\Statement; +use UnicaenImport\Entity\Differentiel\Ligne; +use UnicaenImport\Entity\Differentiel\Query; +use UnicaenImport\Service\Traits\QueryGeneratorServiceAwareTrait; + + +/** + * Classe permettant de récupérer le différentiel entre une table source et une table OSE + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class DifferentielService extends AbstractService +{ + use QueryGeneratorServiceAwareTrait; + + /** + * Statement + * + * @var Statement + */ + protected $stmt; + + /** + * Nom de la table courante + * + * @var string + */ + protected $tableName; + + + + /** + * Construit un différentiel + * + * @param string $query Requête de filtrage + * + * @return self + */ + public function make(Query $query) + { + $this->tableName = $query->getTableName(); + $query->addDefaultSqlCriterion($this->getServiceQueryGenerator()); + $this->stmt = $this->getEntityManager()->getConnection()->executeQuery($query->toSql(), []); + + return $this; + } + + + + /** + * Récupère la prochaine ligne de différentiel + * + * @return Ligne|false + */ + public function fetchNext() + { + $data = $this->stmt->fetch(); + if ($data) return new Ligne($this->getEntityManager(), $this->tableName, $data); + + return false; + } + + + + /** + * Retourne toutes les lignes concernées + * + * @return Ligne[] + */ + public function fetchAll() + { + $result = []; + while ($data = $this->stmt->fetch()) { + if ($data) $result[] = new Ligne($this->getEntityManager(), $this->tableName, $data); + } + + return $result; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/QueryGeneratorService.php b/src/UnicaenImport/Service/QueryGeneratorService.php new file mode 100644 index 0000000..f7f9c05 --- /dev/null +++ b/src/UnicaenImport/Service/QueryGeneratorService.php @@ -0,0 +1,596 @@ +<?php +namespace UnicaenImport\Service; + +use UnicaenImport\Exception\Exception; +use UnicaenImport\Entity\Differentiel\Query; +use UnicaenImport\Options\Traits\ModuleOptionsAwareTrait; +use UnicaenImport\Service\Traits\SchemaServiceAwareTrait; + +/** + * + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class QueryGeneratorService extends AbstractService +{ + use SchemaServiceAwareTrait; + use ModuleOptionsAwareTrait; + + const AG_BEGIN = '-- AUTOMATIC GENERATION --'; + const AG_END = '-- END OF AUTOMATIC GENERATION --'; + const ANNEE_COLUMN_NAME = 'ANNEE_ID'; + + /** + * Tables + * + * @var string[] + */ + protected $tables; + + /** + * Colonnes + * + * @var array + */ + protected $cols = []; + + + + /** + * Retourne la liste des tables importables + * + * @return string[] + */ + protected function getTables() + { + if (empty($this->tables)) { + $this->tables = $this->getServiceSchema()->getImportTables(); + } + + return $this->tables; + } + + + + /** + * Retourne la liste des colonnes importables d'une table + * + * @param string $tableName + * + * @return string[] + */ + protected function getCols($tableName) + { + if (!isset($this->cols[$tableName])) { + $this->cols[$tableName] = $this->getServiceSchema()->getImportCols($tableName); + } + + return $this->cols[$tableName]; + } + + + + public function execMajVM($tableName) + { + $mviewName = $this->escape('MV_' . $tableName); + $sql = "BEGIN DBMS_MVIEW.REFRESH($mviewName, 'C'); END;"; + try { + $this->getEntityManager()->getConnection()->exec($sql); + } catch (\Doctrine\DBAL\DBALException $e) { + throw Exception::duringMajMVException($e, $tableName); + } + } + + + + /** + * Met à jour des données d'après la requête transmise + * + * @param Query $query Requête de filtrage pour la mise à jour + * + * @retun self + */ + public function execMaj(Query $query) + { + $currentUser = $this->getDbUser(); + if (empty($currentUser)) { + throw new Exception('Vous devez être authentifié pour réaliser cette action'); + } + $userId = $this->escape($currentUser->getId()); + $procName = $this->escapeKW('MAJ_' . $query->getTableName()); + $conditions = $query->toSql(false); + if (!empty($conditions)) { + $conditions = $this->escape($conditions); + } else { + $conditions = 'NULL'; + } + $ignoreFields = $query->getIgnoreFields(); + if (empty($ignoreFields)) { + $ignoreFields = 'NULL'; + } else { + $ignoreFields = $this->escape(implode(',', $ignoreFields)); + } + + $sql = "BEGIN ".$this->getPackage().".SET_CURRENT_USER($userId);".$this->getPackage().".$procName($conditions,$ignoreFields); END;"; + try { + $this->getEntityManager()->getConnection()->exec($sql); + } catch (\Doctrine\DBAL\DBALException $e) { + throw Exception::duringMajException($e, $query->getTableName()); + } + + return $this; + } + + + + /** + * Synchronise une table + * + * @param string $tableName + * + * @return string[] + */ + public function syncTable($tableName) + { + $currentUser = $this->getDbUser(); + if (empty($currentUser)) { + throw new Exception('Vous devez être authentifié pour réaliser cette action'); + } + $userId = $this->escape($currentUser->getId()); + + $errors = []; + $lastLogId = $this->getLastLogId(); + $sql = "BEGIN ".$this->getPackage().".SET_CURRENT_USER($userId);".$this->getPackage()."." . $this->escapeKW('MAJ_' . $tableName) . "; END;"; + try { + $this->getEntityManager()->getConnection()->exec($sql); + } catch (\Doctrine\DBAL\DBALException $e) { + $errors[] = Exception::duringMajException($e, $tableName)->getMessage(); + } + $errors = $errors + $this->getLogMessages($lastLogId); + + return $errors; + } + + + + /** + * retourne le dernier ID du log de synchronisation + * + * @return int + */ + protected function getLastLogId() + { + $sql = "SELECT MAX(id) last_log_id FROM SYNC_LOG"; + $stmt = $this->getEntityManager()->getConnection()->executeQuery($sql); + if ($r = $stmt->fetch()) { + return (int)$r['LAST_LOG_ID']; + } + + return 0; + } + + + + /** + * Retourne tous les messages d'erreur qui sont apparue depuis $since + * + * @param int $since + * + * @return string[] + */ + protected function getLogMessages($since) + { + $since = (int)$since; + $sql = "SELECT message FROM sync_log WHERE id > :since ORDER BY id"; + $messages = []; + $stmt = $this->getEntityManager()->getConnection()->executeQuery($sql, ['since' => (int)$since]); + while ($r = $stmt->fetch()) { + $messages[] = $r['MESSAGE']; + } + + return $messages; + } + + + + /** + * + * @param string $tableName + * + * @return null|string + */ + public function getSqlCriterion($tableName) + { + $sql = 'SELECT '.$this->getPackage().'.GET_SQL_CRITERION(' . $this->escape($tableName) . ',\'\') res FROM DUAL'; + $stmt = $this->getEntityManager()->getConnection()->executeQuery($sql); + + if ($r = $stmt->fetch()) { + $res = $r['RES']; + if ($res) return $res; else return null; + } + + return null; + } + + + + /** + * Retourne les identifiants des données concernés + * + * @param string $tableName + * @param string|string[]|null $sourceCode + * @param integer|null $anneeId + * + * @return integer[]|null + */ + public function getIdFromSourceCode($tableName, $sourceCode, $anneeId = null) + { + if (empty($sourceCode)) return null; + + $sql = 'SELECT ID FROM ' . $this->escapeKW($tableName) . ' WHERE SOURCE_CODE IN (:sourceCode)'; + if ($anneeId) { + $sql .= ' AND ANNEE_ID = ' . (string)(int)$anneeId; + } + $stmt = $this->getEntityManager()->getConnection()->executeQuery( + $sql, + ['sourceCode' => (array)$sourceCode], + ['sourceCode' => \Doctrine\DBAL\Connection::PARAM_INT_ARRAY] + ); + if ($r = $stmt->fetch()) { + ; + + return (int)$r['ID']; + } else { + return null; + } + } + + + + /** + * Mettre à jour toutes les infos dans la BDD + * + * @return self + */ + public function updateViewsAndPackages() + { + $views = $this->makeDiffViews(); + + foreach ($views as $vn => $view) { + $this->exec($view); + } + + $declaration = $this->makePackageDeclaration(); + $this->exec($declaration); + + $body = $this->makePackageBody(); + $this->exec($body); + + return $this; + } + + + + protected function getPackage() + { + return $this->getOptionsModule()->getPackage(); + } + + + + /** + * Retourne le code source du package d'import + * + * @return string + */ + protected function getPackageDeclaration() + { + $sql = "SELECT TEXT FROM USER_SOURCE WHERE NAME = '".$this->getPackage()."' AND type = 'PACKAGE'"; + $result = $this->query($sql, [], 'TEXT'); + + return implode("", $result); + } + + + + /** + * Retourne le code source du package d'import + * + * @return string + */ + protected function getPackageBody() + { + $sql = "SELECT TEXT FROM USER_SOURCE WHERE NAME = '".$this->getPackage()."' AND type = 'PACKAGE BODY'"; + $result = $this->query($sql, [], 'TEXT'); + + return implode("", $result); + } + + + + /** + * Construit toutes les vues différentielles + * + * @return array + */ + protected function makeDiffViews() + { + $tables = $this->getTables(); + $result = []; + foreach ($tables as $table) { + $result[$table] = $this->makeDiffView($table); + } + + return $result; + } + + + + /** + * Construit toutes les déclarations de procédures + * + * @return array + */ + protected function makeProcDeclarations() + { + $tables = $this->getTables(); + $result = []; + foreach ($tables as $table) { + $result[$table] = $this->makeProcDeclaration($table); + } + + return $result; + } + + + + /** + * Construit tous les corps de procédures + * + * @return array + */ + protected function makeProcBodies() + { + $tables = $this->getTables(); + $result = []; + foreach ($tables as $table) { + $result[$table] = $this->makeProcBody($table); + } + + return $result; + } + + + + /** + * Constuit la nouvelle déclaration du package IMPORT + * + * @return string + */ + protected function makePackageDeclaration() + { + $src = $this->getPackageDeclaration(); + $decl = implode("\n", $this->makeProcDeclarations()); + + return $this->updatePackageContent($src, $decl); + } + + + + /** + * Constuit la nouvelle déclaration du package IMPORT + * + * @return string + */ + protected function makePackageBody() + { + $src = $this->getPackageBody(); + $decl = implode("\n\n\n\n", $this->makeProcBodies()); + + return $this->updatePackageContent($src, $decl); + } + + + + /** + * Mise à jour du contenu d'un package (déclaration ou corps) + * + * @param string $packageSource + * @param string $newContent + * + * @return string + */ + protected function updatePackageContent($packageSource, $newContent) + { + $src = $packageSource; + if (null === $begin = strpos($packageSource, self::AG_BEGIN)) { + throw new Exception('Le tag indiquant le début de la zone automatique du package n\'a pas été trouvée'); + } + + if (null === $end = strpos($packageSource, self::AG_END)) { + throw new Exception('Le tag indiquant la fin de la zone automatique du package n\'a pas été trouvée'); + } + + $src = 'CREATE OR REPLACE ' + . substr($packageSource, 0, $begin + strlen(self::AG_BEGIN)) + . "\n\n" . $newContent . "\n\n " + . substr($packageSource, $end); + + return $src; + } + + + + /** + * Génère une vue différentielle pour une table donnée + * + * @param string $tableName + * + * @return string + */ + protected function makeDiffView($tableName) + { + // Pour l'annualisation : + $schema = $this->getServiceSchema()->getSchema($tableName); + $joinCond = ''; + $delCond = ''; + $depJoin = ''; + if (array_key_exists(self::ANNEE_COLUMN_NAME, $schema)) { + // Si la table courante est annualisée ... + if ($this->getServiceSchema()->hasColumn('V_DIFF_' . $tableName, self::ANNEE_COLUMN_NAME)) { + // ... et que la source est également annualisée alors concordance nécessaire + $joinCond = ' AND S.' . self::ANNEE_COLUMN_NAME . ' = d.' . self::ANNEE_COLUMN_NAME; + } + // destruction ssi dans l'année d'import courante + $delCond = ' AND d.' . self::ANNEE_COLUMN_NAME . ' = '.$this->getPackage().'.get_current_annee'; + } else { + // on recherche si la table dépend d'une table qui, elle, serait annualisée + foreach ($schema as $columnName => $column) { + /* @var $column \Import\Entity\Schema\Column */ + if (!empty($column->refTableName)) { + $refSchema = $this->getServiceSchema()->getSchema($column->refTableName); + if (!empty($refSchema) && array_key_exists(self::ANNEE_COLUMN_NAME, $refSchema)) { + // Oui, la table dépend d'une table annualisée!! + // Donc, on utilise la table référente + $depJoin = "\n LEFT JOIN " . $column->refTableName . " rt ON rt." . $column->refColumnName . " = d." . $columnName; + // destruction ssi dans l'année d'import courante de la table référente + $delCond = ' AND rt.' . self::ANNEE_COLUMN_NAME . ' = '.$this->getPackage().'.get_current_annee'; + + break; + /* on stoppe à la première table contenant une année. + * S'il en existe une autre tant pis pour elle, + * les années doivent de toute manière être concordantes entres sources!!! + */ + } + } + } + } + + // on génère ensuite la bonne requête !!! + $cols = $this->getCols($tableName); + $sql = "CREATE OR REPLACE FORCE VIEW OSE.V_DIFF_$tableName AS +select diff.* from (SELECT + COALESCE( D.id, S.id ) id, + COALESCE( S.source_id, D.source_id ) source_id, + COALESCE( S.source_code, D.source_code ) source_code, +CASE + WHEN S.source_code IS NOT NULL AND D.source_code IS NULL THEN 'insert' + WHEN S.source_code IS NOT NULL AND D.source_code IS NOT NULL AND (D.histo_destruction IS NULL OR D.histo_destruction > SYSDATE) THEN 'update' + WHEN S.source_code IS NULL AND D.source_code IS NOT NULL AND (D.histo_destruction IS NULL OR D.histo_destruction > SYSDATE)$delCond THEN 'delete' + WHEN S.source_code IS NOT NULL AND D.source_code IS NOT NULL AND D.histo_destruction IS NOT NULL AND D.histo_destruction <= SYSDATE THEN 'undelete' END import_action, + " . $this->formatColQuery($cols, ' CASE WHEN S.source_code IS NULL AND D.source_code IS NOT NULL THEN D.:column ELSE S.:column END :column', ",\n ") . ", + " . $this->formatColQuery($cols, ' CASE WHEN D.:column <> S.:column OR (D.:column IS NULL AND S.:column IS NOT NULL) OR (D.:column IS NOT NULL AND S.:column IS NULL) THEN 1 ELSE 0 END U_:column', ",\n ") . " +FROM + $tableName D$depJoin + FULL JOIN SRC_$tableName S ON S.source_id = D.source_id AND S.source_code = D.source_code$joinCond +WHERE + (S.source_code IS NOT NULL AND D.source_code IS NOT NULL AND D.histo_destruction IS NOT NULL AND D.histo_destruction <= SYSDATE) + OR (S.source_code IS NULL AND D.source_code IS NOT NULL AND (D.histo_destruction IS NULL OR D.histo_destruction > SYSDATE)) + OR (S.source_code IS NOT NULL AND D.source_code IS NULL) + OR " . $this->formatColQuery($cols, 'D.:column <> S.:column OR (D.:column IS NULL AND S.:column IS NOT NULL) OR (D.:column IS NOT NULL AND S.:column IS NULL)', "\n OR ") . " +) diff JOIN source on source.id = diff.source_id WHERE import_action IS NOT NULL AND source.importable = 1"; + + return $sql; + } + + + + /** + * Génère une déclaration de procédure pour une table donnée + * + * @param string $tableName + * + * @return string + */ + protected function makeProcDeclaration($tableName) + { + return " PROCEDURE MAJ_$tableName(SQL_CRITERION CLOB DEFAULT '', IGNORE_UPD_COLS CLOB DEFAULT '');"; + } + + + + /** + * Génère un corps de procédure pour une table donnée + * + * @param string $tableName + * + * @return string + */ + protected function makeProcBody($tableName) + { + $cols = $this->getCols($tableName); + + $sql = " PROCEDURE MAJ_$tableName(SQL_CRITERION CLOB DEFAULT '', IGNORE_UPD_COLS CLOB DEFAULT '') IS + TYPE r_cursor IS REF CURSOR; + sql_query CLOB; + diff_cur r_cursor; + diff_row V_DIFF_$tableName%ROWTYPE; + BEGIN + sql_query := 'SELECT V_DIFF_$tableName.* FROM V_DIFF_$tableName ' || get_sql_criterion('$tableName',SQL_CRITERION); + OPEN diff_cur FOR sql_query; + LOOP + FETCH diff_cur INTO diff_row; EXIT WHEN diff_cur%NOTFOUND; + BEGIN + + CASE diff_row.import_action + WHEN 'insert' THEN + INSERT INTO OSE.$tableName + ( id, " . $this->formatColQuery($cols) . ", source_id, source_code, histo_createur_id, histo_modificateur_id ) + VALUES + ( COALESCE(diff_row.id,$tableName" . "_ID_SEQ.NEXTVAL), " . $this->formatColQuery($cols, 'diff_row.:column') . ", diff_row.source_id, diff_row.source_code, get_current_user, get_current_user ); + + WHEN 'update' THEN + " . $this->formatColQuery( + $cols, + "IF (diff_row.u_:column = 1 AND IN_COLUMN_LIST(':column',IGNORE_UPD_COLS) = 0) THEN UPDATE OSE.$tableName SET :column = diff_row.:column WHERE ID = diff_row.id; END IF;" + , "\n " + ) . " + + WHEN 'delete' THEN + UPDATE OSE.$tableName SET histo_destruction = SYSDATE, histo_destructeur_id = get_current_user WHERE ID = diff_row.id; + + WHEN 'undelete' THEN + " . $this->formatColQuery( + $cols, + "IF (diff_row.u_:column = 1 AND IN_COLUMN_LIST(':column',IGNORE_UPD_COLS) = 0) THEN UPDATE OSE.$tableName SET :column = diff_row.:column WHERE ID = diff_row.id; END IF;" + , "\n " + ) . " + UPDATE OSE.$tableName SET histo_destruction = NULL, histo_destructeur_id = NULL WHERE ID = diff_row.id; + + END CASE; + + EXCEPTION WHEN OTHERS THEN + ".$this->getPackage().".SYNC_LOG( SQLERRM, '$tableName', diff_row.source_code ); + END; + END LOOP; + CLOSE diff_cur; + + END MAJ_$tableName;"; + + return $sql; + } + + + + /** + * Retourne une chaîne SQL correspondant, pour chaque colonne donnée, au résultat du formatage donné, + * concaténé selon le séparateur transmis. + * + * L'opérateur $c permet de situer l'endroit où devont être placées les colonnes. + * + * @param array $cols + * @param string $format + * @param string $separator + * + * @return string + */ + protected function formatColQuery(array $cols, $format = ':column', $separator = ',') + { + $res = []; + foreach ($cols as $col) { + $res[] = str_replace(':column', $col, $format); + } + + return implode($separator, $res); + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/SchemaService.php b/src/UnicaenImport/Service/SchemaService.php new file mode 100644 index 0000000..0497c4f --- /dev/null +++ b/src/UnicaenImport/Service/SchemaService.php @@ -0,0 +1,146 @@ +<?php +namespace UnicaenImport\Service; + +use UnicaenImport\Exception\Exception; +use UnicaenImport\Entity\Schema\Column; + +/** + * + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class SchemaService extends AbstractService +{ + /** + * Schéma + * + * @var array + */ + protected $schema; + + + + + + /** + * Retourne le schéma de la BDD + * + * @return array + */ + public function getSchema( $tableName=null ) + { + if (empty($this->schema)){ + $this->schema = $this->makeSchema(); + } + if (empty($tableName)){ + return $this->schema; + }elseif(array_key_exists($tableName, $this->schema)){ + return $this->schema[$tableName]; + }else{ + return null; + } + } + + + + /** + * @return Column[][] + */ + public function makeSchema() + { + $sql = 'SELECT * FROM V_IMPORT_TAB_COLS'; + $d = $this->query( $sql, [] ); + + $sc = []; + foreach( $d as $col ){ + $column = new Column; + $column->dataType = $col['DATA_TYPE']; + $column->length = (null === $col['LENGTH']) ? null : (integer)$col['LENGTH']; + $column->nullable = $col['NULLABLE'] == '1'; + $column->hasDefault = $col['HAS_DEFAULT'] == '1'; + $column->refTableName = $col['C_TABLE_NAME']; + $column->refColumnName = $col['C_COLUMN_NAME']; + $column->importActif = $col['IMPORT_ACTIF'] == '1'; + $sc[$col['TABLE_NAME']][$col['COLUMN_NAME']] = $column; + } + return $sc; + } + + + + /** + * retourne la liste des tables supportées par l'import automatique + * + * @return array + */ + public function getImportTables() + { + $sql = "SELECT SUBSTR(name,5) as TABLE_NAME FROM ( + SELECT mview_name AS name FROM USER_MVIEWS + UNION SELECT view_name AS name FROM USER_VIEWS + UNION SELECT TABLE_NAME AS name FROM USER_TABLES + ) t JOIN user_tables ut ON (ut.table_name = SUBSTR(name,5)) + WHERE name LIKE 'SRC_%'"; + return $this->query( $sql, [], 'TABLE_NAME'); + } + + /** + * Retourne la liste des tables ayant des vues matérialisées + * + * @return string[] + */ + public function getImportMviews() + { + $sql = "SELECT mview_name FROM USER_MVIEWS WHERE mview_name LIKE 'MV_%'"; + $stmt = $this->getEntityManager()->getConnection()->query($sql); + $mviews = []; + while ($d = $stmt->fetch()){ + $mvn = substr( $d['MVIEW_NAME'], 3 ); + $mviews[] = $mvn; + } + return $mviews; + } + + /** + * + * @param string $tableName + * @param string $columnName + */ + public function hasColumn( $tableName, $columnName ) + { + $sql = " + SELECT + COUNT(*) result + FROM + USER_TAB_COLS utc + WHERE + utc.table_name = :tableName + AND utc.column_name = :columnName + "; + $result = $this->query( $sql, compact('tableName', 'columnName'), 'RESULT'); + return $result[0] === '1'; + } + + /** + * Retourne les colonnes concernées par l'import pour une table donnée + */ + public function getImportCols( $tableName ) + { + $sql = " + SELECT + utc.COLUMN_NAME + FROM + USER_TAB_COLS utc + JOIN ALL_TAB_COLS atc ON (atc.table_name = 'SRC_' || utc.table_name AND atc.column_name = utc.column_name) + WHERE + utc.COLUMN_NAME NOT IN ('ID') + AND utc.COLUMN_NAME NOT LIKE 'HISTO_%' + AND utc.COLUMN_NAME NOT LIKE 'SOURCE_%' + AND utc.table_name = :tableName + ORDER BY + utc.COLUMN_NAME"; + + return $this->query( $sql, ['tableName' => $tableName], 'COLUMN_NAME'); + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/Traits/DifferentielServiceAwareTrait.php b/src/UnicaenImport/Service/Traits/DifferentielServiceAwareTrait.php new file mode 100644 index 0000000..59b2088 --- /dev/null +++ b/src/UnicaenImport/Service/Traits/DifferentielServiceAwareTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace UnicaenImport\Service\Traits; + +use UnicaenImport\Service\DifferentielService; +use RuntimeException; + +/** + * Description of DifferentielServiceAwareTrait + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +trait DifferentielServiceAwareTrait +{ + /** + * @var DifferentielService + */ + private $serviceDifferentiel; + + + + /** + * @param DifferentielService $serviceDifferentiel + * + * @return self + */ + public function setServiceDifferentiel(DifferentielService $serviceDifferentiel) + { + $this->serviceDifferentiel = $serviceDifferentiel; + + return $this; + } + + + + /** + * @return DifferentielService + * @throws RuntimeException + */ + public function getServiceDifferentiel() + { + if (empty($this->serviceDifferentiel)) { + if (!method_exists($this, 'getServiceLocator')) { + throw new RuntimeException('La classe ' . get_class($this) . ' n\'a pas accès au ServiceLocator.'); + } + + $serviceLocator = $this->getServiceLocator(); + if (method_exists($serviceLocator, 'getServiceLocator')) { + $serviceLocator = $serviceLocator->getServiceLocator(); + } + $this->serviceDifferentiel = $serviceLocator->get('UnicaenImport\Service\Differentiel'); + } + + return $this->serviceDifferentiel; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/Traits/QueryGeneratorServiceAwareTrait.php b/src/UnicaenImport/Service/Traits/QueryGeneratorServiceAwareTrait.php new file mode 100644 index 0000000..2586f98 --- /dev/null +++ b/src/UnicaenImport/Service/Traits/QueryGeneratorServiceAwareTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace UnicaenImport\Service\Traits; + +use UnicaenImport\Service\QueryGeneratorService; +use RuntimeException; + +/** + * Description of QueryGeneratorServiceAwareTrait + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +trait QueryGeneratorServiceAwareTrait +{ + /** + * @var QueryGeneratorService + */ + private $serviceQueryGenerator; + + + + /** + * @param QueryGeneratorService $serviceQueryGenerator + * + * @return self + */ + public function setServiceQueryGenerator(QueryGeneratorService $serviceQueryGenerator) + { + $this->serviceQueryGenerator = $serviceQueryGenerator; + + return $this; + } + + + + /** + * @return QueryGeneratorService + * @throws RuntimeException + */ + public function getServiceQueryGenerator() + { + if (empty($this->serviceQueryGenerator)) { + if (!method_exists($this, 'getServiceLocator')) { + throw new RuntimeException('La classe ' . get_class($this) . ' n\'a pas accès au ServiceLocator.'); + } + + $serviceLocator = $this->getServiceLocator(); + if (method_exists($serviceLocator, 'getServiceLocator')) { + $serviceLocator = $serviceLocator->getServiceLocator(); + } + $this->serviceQueryGenerator = $serviceLocator->get('UnicaenImport\Service\QueryGenerator'); + } + + return $this->serviceQueryGenerator; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/Service/Traits/SchemaServiceAwareTrait.php b/src/UnicaenImport/Service/Traits/SchemaServiceAwareTrait.php new file mode 100644 index 0000000..65f4adf --- /dev/null +++ b/src/UnicaenImport/Service/Traits/SchemaServiceAwareTrait.php @@ -0,0 +1,56 @@ +<?php + +namespace UnicaenImport\Service\Traits; + +use UnicaenImport\Service\SchemaService; +use RuntimeException; + +/** + * Description of SchemaServiceAwareTrait + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +trait SchemaServiceAwareTrait +{ + /** + * @var SchemaService + */ + private $serviceSchema; + + + + /** + * @param SchemaService $serviceSchema + * + * @return self + */ + public function setServiceSchema(SchemaService $serviceSchema) + { + $this->serviceSchema = $serviceSchema; + + return $this; + } + + + + /** + * @return SchemaService + * @throws RuntimeException + */ + public function getServiceSchema() + { + if (empty($this->serviceSchema)) { + if (!method_exists($this, 'getServiceLocator')) { + throw new RuntimeException('La classe ' . get_class($this) . ' n\'a pas accès au ServiceLocator.'); + } + + $serviceLocator = $this->getServiceLocator(); + if (method_exists($serviceLocator, 'getServiceLocator')) { + $serviceLocator = $serviceLocator->getServiceLocator(); + } + $this->serviceSchema = $serviceLocator->get('UnicaenImport\Service\Schema'); + } + + return $this->serviceSchema; + } +} \ No newline at end of file diff --git a/src/UnicaenImport/View/Helper/DifferentielLigne/DifferentielLigne.php b/src/UnicaenImport/View/Helper/DifferentielLigne/DifferentielLigne.php new file mode 100644 index 0000000..290b5ed --- /dev/null +++ b/src/UnicaenImport/View/Helper/DifferentielLigne/DifferentielLigne.php @@ -0,0 +1,225 @@ +<?php +namespace UnicaenImport\View\Helper\DifferentielLigne; + +use UnicaenImport\Options\Traits\ModuleOptionsAwareTrait; +use Zend\View\Helper\AbstractHelper; +use UnicaenImport\Entity\Differentiel\Ligne; +use Zend\ServiceManager\ServiceLocatorAwareInterface; +use Zend\ServiceManager\ServiceLocatorAwareTrait; + +/** + * Aide de vue permettant d'afficher une ligne de différentiel d'import + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class DifferentielLigne extends AbstractHelper implements ServiceLocatorAwareInterface +{ + use ServiceLocatorAwareTrait; + use ModuleOptionsAwareTrait; + + /** + * @var Ligne + */ + protected $ligne; + + + + /** + * Helper entry point. + * + * @return self + */ + final public function __invoke(Ligne $ligne) + { + $filter = new \Zend\Filter\Word\UnderscoreToCamelCase; + + $classes = $this->getOptionsModule()->getDifferentielViewHelpers(); + $helperObject = null; + if (isset($classes[$ligne->getTableName()])) { + $helperClass = $classes[$ligne->getTableName()]; + $helperObject = new $helperClass; + if (!$helperObject instanceof self) { + throw new \LogicException('L\'aide de vue Import pour la table ' . $ligne->getTableName() . ' doit hériter de ' . __CLASS__); + } + } + + $helperClass = __NAMESPACE__ . '\\' . $filter->filter(strtolower($ligne->getTableName())); + + if ($helperObject) { + $helperObject->setServiceLocator($this->getServiceLocator()); // transmission du serviceLocator + $helperObject->setLigne($ligne); + $helperObject->setView($this->getView()); + + return $helperObject; + } else { + $this->setLigne($ligne); + + return $this; + } + } + + + + /** + * Retourne le code HTML généré par cette aide de vue. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } + + + + /** + * Génère le code HTML. + * + * @return string + */ + protected function render() + { + $out = $this->getType() . ' ' . $this->getSujet() . ' ' . $this->getAction() . ' depuis ' . $this->getSource() . '<br />'; + $details = $this->getDetails(); + if (!empty($details)) $out .= 'Détails : ' . implode(', ', $details) . ''; + + return (string)$this->getView()->messenger()->setMessage($out, \UnicaenApp\View\Helper\Messenger::WARNING); + } + + + + /** + * Retourne le type de ligne (en fonction du nom de la table) + * + * @return string + */ + public function getType() + { + $type = ucwords(str_replace('_', ' ', strtolower($this->ligne->getTableName()))); + + return $type; + } + + + + /** + * Retourne le sujet de la ligne + * + * @return string + */ + public function getSujet() + { + return 'Code initial : ' . $this->ligne->getSourceCode(); + } + + + + /** + * Retourne l'action à effectuer pour que la mise à jour s'effectue + * + * @return string + */ + public function getAction() + { + switch ($this->ligne->getAction()) { + case 'insert' : + return 'à importer'; + case 'update' : + return 'à mettre à jour'; + case 'delete' : + return 'à supprimer'; + case 'undelete' : + return 'à restaurer'; + } + + return 'Action non définie'; + } + + + + /** + * Retourne les détails de l'action à effectuer + * + * @return string[] + */ + public function getDetails() + { + $details = []; + if ('update' == $this->ligne->getAction()) { + $changes = $this->ligne->getChanges(); + foreach ($changes as $column => $value) { + $columnDetails = $this->getColumnDetails($column, $value); + if ($columnDetails) $details[] = $columnDetails; + } + } + + return $details; + } + + + + public function getColumnDetails($column, $value) + { + switch ($column) { + case 'VALIDITE_DEBUT': + if ($value) { + $date = new \DateTime($value); + + return 'valide depuis le ' . $date->format('d/m/Y'); + } else { + return 'valide depuis toujours'; + } + case 'VALIDITE_FIN': + if ($value) { + $date = new \DateTime($value); + + return 'valide jusqu\'au ' . $date->format('d/m/Y'); + } else { + return 'valide pour toujours'; + } + default: + $column = str_replace('_', ' ', strtolower($column)); + + return $column . ' devient ' . $value; + } + } + + + + /** + * Retourne la source de données + * + * @return string + */ + public function getSource() + { + return $this->ligne->getSource()->getLibelle(); + } + + + + /** + * + * @return Ligne + */ + public function getLigne() + { + return $this->ligne; + } + + + + /** + * + * @param Ligne $ligne + * + * @return DifferentielLigne + */ + public function setLigne(Ligne $ligne) + { + $this->ligne = $ligne; + + return $this; + } + +} \ No newline at end of file diff --git a/src/UnicaenImport/View/Helper/DifferentielListe.php b/src/UnicaenImport/View/Helper/DifferentielListe.php new file mode 100644 index 0000000..d6934a0 --- /dev/null +++ b/src/UnicaenImport/View/Helper/DifferentielListe.php @@ -0,0 +1,122 @@ +<?php +namespace UnicaenImport\View\Helper; + +use Zend\View\Helper\AbstractHelper; +use UnicaenImport\Service\Differentiel; +use UnicaenImport\Entity\Differentiel\Ligne; +use UnicaenImport\Exception\Exception; +use UnicaenImport\View\Helper\DifferentielLigne\DifferentielLigne; + +/** + * Aide de vue permettant d'afficher une liste de données différentielles + * + * @author Laurent LÉCLUSE <laurent.lecluse at unicaen.fr> + */ +class DifferentielListe extends AbstractHelper +{ + /** + * Lignes de différentiel + * + * @var Ligne[] + */ + protected $lignes; + + + + + + /** + * Helper entry point. + * + * @param Ligne[]|Differentiel $lignes + * @return self + */ + final public function __invoke( $lignes ) + { + $this->setLignes($lignes); + return $this; + } + + /** + * Retourne le code HTML généré par cette aide de vue. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * Génère le code HTML. + * + * @return string + */ + public function render(){ + $aucunEcart = 'Il n\'y a aucun écart entre les sources de données et OSE'; + if (empty($this->lignes)) return $aucunEcart; + $out = ''; + foreach( $this->lignes as $ligne ){ + $dl = $this->getView()->differentielLigne( $ligne ); + if ($ligne->getAction() != 'update' || $dl->getDetails()){ + $out .= '<tr>' + .'<td>'.$dl->getType().'</td>' + .'<td>'.$dl->getSujet().'</td>' + .'<td>'.ucfirst($dl->getAction()).'</td>' + .'<td>'.$dl->getSource().'</td>' + .'<td>'.ucfirst(implode( ', ', $dl->getDetails() )).'</td>' + .'</tr>'."\n"; + } + } + if ($out){ + $out = '<table class="table">'."\n" + .'<tr><th>Type</th><th>Sujet</th><th>Action</th><th>Source</th><th>Détails</th></tr>' + .$out + .'</table>'."\n"; + }else{ + return $aucunEcart; + } + return $out; + } + + /** + * Retourne la liste des lignes + * + * @return Ligne[] + */ + public function getLignes() + { + return $this->lignes; + } + + public function addLigne( Ligne $ligne ) + { + $this->lignes[] = $ligne; + } + + /** + * + * + * @param Ligne[]|Differentiel $lignes + * @return DifferentielLigne + */ + public function setLignes($lignes) + { + $this->lignes = []; + if( $lignes instanceof Differentiel ){ + while( $ligne = $lignes->fetchNext() ){ + $this->addLigne($ligne); + } + }elseif(is_array($lignes)){ + foreach( $lignes as $ligne ){ + if (! $ligne instanceof Ligne){ + throw new Exception('La ligne de différentiel transmise n\'est pas au bon format.'); + } + $this->addLigne( $ligne ); + } + } + return $this; + } + + +} \ No newline at end of file diff --git a/view/unicaen-import/import/config.phtml b/view/unicaen-import/import/config.phtml new file mode 100644 index 0000000..82cfc49 --- /dev/null +++ b/view/unicaen-import/import/config.phtml @@ -0,0 +1,3 @@ +<h1>Import de données</h1> + +TEST CONFIG = <?php echo $test ?> \ No newline at end of file diff --git a/view/unicaen-import/import/index.phtml b/view/unicaen-import/import/index.phtml new file mode 100644 index 0000000..042ffd7 --- /dev/null +++ b/view/unicaen-import/import/index.phtml @@ -0,0 +1,10 @@ +<?php + +$this->headTitle()->append("Import de données"); + +?> +<h1 class="page-header">Import de données</h1> + +<?php + +echo $this->navigation('navigation')->menuContextuel()->setPartial('unicaen-app/menu-dl.phtml'); \ No newline at end of file diff --git a/view/unicaen-import/import/show-diff.phtml b/view/unicaen-import/import/show-diff.phtml new file mode 100644 index 0000000..7fb3cf2 --- /dev/null +++ b/view/unicaen-import/import/show-diff.phtml @@ -0,0 +1,56 @@ +<?php + +use UnicaenImport\Provider\Privilege\Privileges; + +?> +<h1>Écarts entre l'application et ses sources</h1> + +<?php foreach( $data as $table => $lignes ): + $tableLabel = ucwords(str_replace( '_', ' ', strtolower($table))); +?> +<h2><?php echo $tableLabel ?></h2> +<div id="DIV_<?php echo $table ?>" data-url="<?php echo $this->url('import', ['action' => 'update','table' => $table]) ?>"> +<?php echo $this->differentielListe( $lignes )->render(); ?> + +<?php if (count($lignes) > 100) : ?> +<div>Toutes les lignes n'ont pas été affichées, leur nombre dépassant 100</div> +<?php endif; ?> + +<?php if ($this->isAllowed(Privileges::getResourceId(Privileges::IMPORT_MAJ))): ?> +<div> +<?php if (in_array($table, $mviews)): ?> + <a class="import-update-mv" href="javascript:void(0)" data-table="<?php echo $table ?>"> + <span class="glyphicon glyphicon-refresh"></span> + Mettre à jour la vue matérialisée + </a> +<?php endif; ?> +<?php if (count($lignes) > 0) : ?> + <a class="import-update" href="javascript:void(0)" data-table="<?php echo $table ?>"> + <span class="glyphicon glyphicon-refresh"></span> + Mettre à jour les données + </a> +<?php endif; ?> +</div> +<div class="alert alert-info" role="alert" id="DIV_WAIT_<?php echo $table ?>" style="display:none"> + <span class="loading" style="padding-right:1em"> </span> + Traitement en cours, merci de patienter. L'opération peut prendre jusqu'à plusieurs minutes. +</div> +<?php endif; ?> +</div> +<?php endforeach; ?> + +<script> + $(function() { + + $("body").on("click", "a.import-update-mv", function(e) { + $( "#DIV_WAIT_"+$(this).data('table') ).show(); + $( "#DIV_"+$(this).data('table') ).refresh({'type-maj': 'vue-materialisee'}); + }); + + $("body").on("click", "a.import-update", function(e) { + $( "#DIV_WAIT_"+$(this).data('table') ).show(); + $( "#DIV_"+$(this).data('table') ).refresh({'type-maj': 'donnees'}); + }); + + }); +</script> \ No newline at end of file diff --git a/view/unicaen-import/import/show-import-tbl.phtml b/view/unicaen-import/import/show-import-tbl.phtml new file mode 100644 index 0000000..6642a40 --- /dev/null +++ b/view/unicaen-import/import/show-import-tbl.phtml @@ -0,0 +1,33 @@ + +<h1>Tableau de bord principal</h1> + +<?php foreach( $data as $tname => $columns ): ?> + <h2><?php echo $tname ?></h2> + <table class="table table-striped table-bordered table-hover"> + <tr> + <th>Colonne</th> + <th>Type</th> + <th>Longueur</th> + <th>Nullable</th> + <th>Val. par défaut</th> + <th>Table de réf.</th> + <th>Colonne de réf.</th> + <th>Import actif</th> + </tr> + <?php foreach( $columns as $cname => $column ): ?> + <tr class="<?php + if (! $column->importActif && ! $column->hasDefault && ! $column->nullable) echo 'danger'; + elseif (! $column->importActif) echo 'warning'; + ?>"> + <th><?php echo $cname ?></th> + <td><?php echo $column->dataType ?></td> + <td style="text-align:center"><?php echo $column->length ?></td> + <td style="text-align:center"><?php echo $column->nullable ? '<span class="glyphicon glyphicon-ok"></span>' : '' ?></td> + <td style="text-align:center"><?php echo $column->hasDefault ? '<span class="glyphicon glyphicon-ok"></span>' : '' ?></td> + <td><?php echo $column->refTableName ?></td> + <td><?php echo $column->refColumnName ?></td> + <td style="text-align:center"><?php echo $column->importActif ? '<span class="glyphicon glyphicon-ok"></span>' : '' ?></td> + </tr> + <?php endforeach; ?> + </table> +<?php endforeach; ?> diff --git a/view/unicaen-import/import/update-materialized-view.php b/view/unicaen-import/import/update-materialized-view.php new file mode 100644 index 0000000..3ac00fa --- /dev/null +++ b/view/unicaen-import/import/update-materialized-view.php @@ -0,0 +1,8 @@ +<?php + +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + diff --git a/view/unicaen-import/import/update-tables.phtml b/view/unicaen-import/import/update-tables.phtml new file mode 100644 index 0000000..f85e95b --- /dev/null +++ b/view/unicaen-import/import/update-tables.phtml @@ -0,0 +1 @@ +<?php echo $this->messenger()->setMessage($message, \UnicaenApp\View\Helper\Messenger::SUCCESS); ?> \ No newline at end of file diff --git a/view/unicaen-import/import/update-views-and-packages.phtml b/view/unicaen-import/import/update-views-and-packages.phtml new file mode 100644 index 0000000..f85e95b --- /dev/null +++ b/view/unicaen-import/import/update-views-and-packages.phtml @@ -0,0 +1 @@ +<?php echo $this->messenger()->setMessage($message, \UnicaenApp\View\Helper\Messenger::SUCCESS); ?> \ No newline at end of file diff --git a/view/unicaen-import/import/update.phtml b/view/unicaen-import/import/update.phtml new file mode 100644 index 0000000..8247233 --- /dev/null +++ b/view/unicaen-import/import/update.phtml @@ -0,0 +1,28 @@ +<?php echo $this->differentielListe( $lignes ); ?> +<?php if (count($lignes) > 100) : ?> +<div>Toutes les lignes n'ont pas été affichées, leur nombre dépassant 100</div> +<?php endif; ?> + +<div> + <a class="import-update-mv" href="javascript:void(0)" data-table="<?php echo $table ?>"> + <span class="glyphicon glyphicon-refresh"></span> + Mettre à jour la vue matérialisée + </a> +<?php if (count($lignes) > 0) : ?> + <a class="import-update" href="javascript:void(0)" data-table="<?php echo $table ?>"> + <span class="glyphicon glyphicon-refresh"></span> + Mettre à jour les données + </a> +<?php endif; ?> +</div> +<div class="alert alert-info" role="alert" id="DIV_WAIT_<?php echo $table ?>" style="display:none"> + <span class="loading" style="padding-right:1em"> </span> + Traitement en cours, merci de patienter. L'opération peut prendre jusqu'à plusieurs minutes. +</div> +<?php + +if ($errors){ + echo $this->messenger()->setMessages([UnicaenApp\View\Helper\Messenger::ERROR => $errors]); +}else{ + echo $this->messenger()->setMessages([UnicaenApp\View\Helper\Messenger::SUCCESS => ['Action réalisée avec succès']]); +} \ No newline at end of file -- GitLab