src/Controller/SystemController.php line 89

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Form\System\EntityGeneration;
  4. use App\Util\VaciFacilController;
  5. use Exception;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. class SystemController extends VaciFacilController
  10. {
  11.     private const DOC_ENTITIES_PATH "private/doctrine-entities.json";
  12.     private const DOC_ENTITIES_TEMP "private/temp";
  13.     private const LOGGER = [
  14.         ["Register""DATETIME"false],
  15.         ["LastModification""DATETIME"true],
  16.         ["WhoPerformed""VARCHAR(50)"false],
  17.         ["Version""INT"false]
  18.     ];
  19.     public  const PHP 0;
  20.     public  const SQLServer 1;
  21.     private const MAP_NAMESPACE = [
  22.         "vaciFacil" => "vfTest",
  23.     ];
  24.     private const MAP_TYPES = [
  25.         "smallint" => [
  26.             self::PHP => "int",
  27.             self::SQLServer => "SMALLINT"
  28.         ],
  29.         "integer" => [
  30.             self::PHP => "int",
  31.             self::SQLServer => "INT"
  32.         ],
  33.         "bigint" => [
  34.             self::PHP => "string",
  35.             self::SQLServer => "BIGINT"
  36.         ],
  37.         "decimal" => [
  38.             self::PHP => "float",
  39.             self::SQLServer => "DECIMAL"
  40.         ],
  41.         "float" => [
  42.             self::PHP => "float",
  43.             self::SQLServer => "FLOAT"
  44.         ],
  45.         "string" => [
  46.             self::PHP => "string",
  47.             self::SQLServer => [
  48.                 false => "VARCHAR",
  49.                 true => "CHAR"
  50.             ]
  51.         ],
  52.         "text" => [
  53.             self::PHP => "string",
  54.             self::SQLServer => "VARCHAR(MAX)"
  55.         ],
  56.         "date" => [
  57.             self::PHP => "\Date",
  58.             self::SQLServer => "DATE"
  59.         ],
  60.         "datetime" => [
  61.             self::PHP => "\DateTime",
  62.             self::SQLServer => "DATETIME"
  63.         ],
  64.         "time" => [
  65.             self::PHP => "\Time",
  66.             self::SQLServer => "TIME"
  67.         ],
  68.         "entity" => [
  69.             self::PHP => "mixed"
  70.         ]
  71.     ];
  72.     /**
  73.      * @Route(
  74.      *     "/{menu}/{item}/{action}",
  75.      *     methods={"GET", "POST"},
  76.      *     name="system",
  77.      *     requirements={"menu"="system"}
  78.      * )
  79.      * @param Request $request
  80.      * @return Response
  81.      */
  82.     public function system(Request $request): Response
  83.     {
  84.         return $this->defaultHandler($request);
  85.     }
  86.     protected function class_generate_GET(Request $request, array $menu): Response
  87.     {
  88.         // Set the path of the doctrine entities file
  89.         $path join("/", [$this->getParameter("kernel.project_dir"), self::DOC_ENTITIES_PATH]);
  90.         try {
  91.             // Read the doctrine entities file
  92.             $entities json_decode(file_get_contents($path), true);
  93.         } catch (Exception) {
  94.             $entities = [];
  95.         }
  96.         // Set the form data
  97.         $formData = ["entity" => $entities];
  98.         // Set the form options
  99.         $options $this->getFormOptions($request, [], ["data" => $formData], ["onsubmit" => "return checkForm()"]);
  100.         // Create the form object
  101.         $form $this->defaultForm(EntityGeneration::class, $request$options);
  102.         // Set the template used to rende the form
  103.         $template $this->getTemplate(EntityGeneration::NAME$menu);
  104.         // Set the form parameters
  105.         $param $this->getTemplateParameters($form$request$menu, ["buttonsOnTop" => true"entities" => $entities]);
  106.         // Return the rendered form
  107.         return $this->render($template$param);
  108.     }
  109.     protected function class_generate_POST(Request $request, array $menu): Response
  110.     {
  111.         // Criteria used to sort the entities and properties
  112.         $ordering = fn($self$other) => $self["order"] <=> $other["order"];
  113.         // Get the project directory
  114.         $project $this->getParameter("kernel.project_dir");
  115.         // Process form and get the data
  116.         $this->defaultForm(EntityGeneration::class, $request$options$data);
  117.         // It will happen when the form is invalid
  118.         if ($data instanceof Response) return $data;
  119.         // Initialize the script content
  120.         list($drops$script$after) = [[], [], []];
  121.         // Sort the entities according the submitted order
  122.         uasort($data["entity"], $ordering);
  123.         // Goes through the entities marked as generate
  124.         foreach (array_filter($data["entity"], fn($item) => $item["generate"]) as $alias => $entity) {
  125.             // Check if the property was removed
  126.             if (is_null($entity["name"])) { continue; }
  127.             // Sort the properties according the submitted order
  128.             uasort($entity["properties"], $ordering);
  129.             usort($data["entity"][$alias]["properties"], $ordering);
  130.             // Check if the selected actions include the entity generation
  131.             if (in_array(1$data["actions"])) {
  132.                 try {
  133.                     // Get the entity code
  134.                     $class $this->generateEntity($data["namespace"], $alias$entity);
  135.                     // Write the entity code in the temporary directory
  136.                     $this->writeFile($class$alias"Entity"$data["namespace"]);
  137.                     // Check if the user want to generate the entity for the development environment
  138.                     if ($data["test"]) {
  139.                         // Get the entity code
  140.                         $class $this->generateEntity($data["namespace"], $alias$entitytrue);
  141.                         // Write the entity code in the temporary directory
  142.                         $this->writeFile($class$alias"Entity"self::MAP_NAMESPACE[$data["namespace"]]);
  143.                     }
  144.                     // Set the operation for the log message
  145.                     $operation $this->translator->trans("inline.operation.entityGeneration");
  146.                     // Add the log message to the flash bag
  147.                     $msg $this->translator->trans("success.0x009", ["%operation%" => $operation"%object%" => $entity["name"]]);
  148.                     $this->addFlash("success"$msg);
  149.                 } catch (Exception $ex) {
  150.                     $this->addFlash("danger"$ex->getMessage());
  151.                 }
  152.             }
  153.             // Check if the selected actions include the repository generation
  154.             if (in_array(2$data["actions"])) {
  155.                 try {
  156.                     // Get the repository code
  157.                     $rep $this->generateRepository($data["namespace"], $alias);
  158.                     // Write the repository code in the temporary directory
  159.                     $this->writeFile($rep$alias"Repository"$data["namespace"]);
  160.                     // Check if the user want to generate the repository for the development environment
  161.                     if ($data["test"]) {
  162.                         // Get the repository code
  163.                         $rep $this->generateRepository(self::MAP_NAMESPACE[$data["namespace"]], $alias);
  164.                         // Write the repository code in the temporary directory
  165.                         $this->writeFile($rep$alias"Repository"self::MAP_NAMESPACE[$data["namespace"]]);
  166.                     }
  167.                     // Set the operation for the log message
  168.                     $operation $this->translator->trans("inline.operation.repositoryGeneration");
  169.                     // Add the log message to the flash bag
  170.                     $msg $this->translator->trans("success.0x009", ["%operation%" => $operation"%object%" => $entity["name"]]);
  171.                     $this->addFlash("success"$msg);
  172.                 } catch (Exception $ex) {
  173.                     $this->addFlash("danger"$ex->getMessage());
  174.                 }
  175.             }
  176.             // Check if the selected actions include the database script generation
  177.             if (in_array(3$data["actions"])) {
  178.                 try {
  179.                     // Get the code to the current entity and append it to the script
  180.                     $this->generateScript($drops$script$after$alias$entity$data["entity"], $data["platform"]);
  181.                     // Set the operation for the log message
  182.                     $operation $this->translator->trans("inline.operation.scriptGeneration");
  183.                     // Add the log message to the flash bag
  184.                     $msg $this->translator->trans("success.0x009", ["%operation%" => $operation"%object%" => $entity["name"]]);
  185.                     $this->addFlash("success"$msg);
  186.                 } catch (Exception $ex) {
  187.                     $this->addFlash("danger"$ex->getMessage());
  188.                 }
  189.             }
  190.             // Check if it needs to unmark the generation flag of the selected entities
  191.             if ($data["invert"]) {
  192.                 $data["entity"][$alias]["generate"] = false;
  193.             }
  194.         }
  195.         // Check if the script have content
  196.         if (!empty($script)) {
  197.             // Used the name the script
  198.             $time = (new \DateTime())->format("Ymd");
  199.             // Set the name of the script
  200.             $fileName "$time.sql";
  201.             // Set the path of the script
  202.             $path join("/", [$projectself::DOC_ENTITIES_TEMP$fileName]);
  203.             // Create the file content
  204.             $content implode("\n"array_reverse($drops));
  205.             $content = ($content "\n\n" implode("\n"$script));
  206.             if (!empty($after)) {
  207.                 $content = ($content "\n" implode("\n"$after));
  208.             }
  209.             // Write the script content to the file
  210.             file_put_contents($path$content);
  211.         }
  212.         // Check if it needs to save the submitted information
  213.         if ($data["alterFile"]) {
  214.             foreach ($data["entity"] as $alias => $entity) {
  215.                 if (is_null($entity["name"])) {
  216.                     unset($data["entity"][$alias]);
  217.                     continue;
  218.                 }
  219.                 $removed false;
  220.                 foreach ($entity["properties"] as $index => $property) {
  221.                     if (is_null($property["name"])) {
  222.                         unset($data["entity"][$alias]["properties"][$index]);
  223.                         $removed true;
  224.                     }
  225.                 }
  226.                 if ($removed) {
  227.                     $data["entity"][$alias]["properties"] = array_values($data["entity"][$alias]["properties"]);
  228.                 }
  229.             }
  230.             // Set the path of the entities file
  231.             $path join("/", [$projectself::DOC_ENTITIES_PATH]);
  232.             // Write the submitted data to the file
  233.             file_put_contents($pathjson_encode($data["entity"], JSON_PRETTY_PRINT));
  234.         }
  235.         return $this->redirect($this->generateUrl($request->get("_route"), $menu));
  236.     }
  237.     /**
  238.      * Generates the code for the class that represent the entity
  239.      * @param string $application used to define the class namespace
  240.      * @param string $alias the entity alias (in general, have 3 letters)
  241.      * @param array $entity the entity properties with their attributes
  242.      * @param bool $development indicates if is a generation for development class
  243.      * @return string the code to be written in the file
  244.      */
  245.     private function generateEntity(string $applicationstring $alias, array $entitybool $development=false): string
  246.     {
  247.         // Set class that the new class will extend
  248.         $extends $entity["logger"] ? "Logger" "SimpleEntity";
  249.         // Attributes ignored in the ORM\Column annotation
  250.         $attrToIgnore = ["id""order""unique""generated""externalAlias"];
  251.         // Set the development namespace
  252.         $mappedNamespace self::MAP_NAMESPACE[$application];
  253.         // Goes through the entity properties
  254.         foreach ($entity["properties"] as $attributes) {
  255.             // Check if the property was removed
  256.             if (is_null($attributes["type"])) { continue; }
  257.             // Get the php type of the property
  258.             $type self::MAP_TYPES[$attributes["type"]][self::PHP];
  259.             // Get the property with the first letter in the upper case
  260.             $ucfName ucfirst($attributes["name"]);
  261.             // Check if the column is part of a unique constraint
  262.             if ($attributes["unique"]) {
  263.                 if ($attributes["type"] == "entity") {
  264.                     foreach ($entity["joins"][$attributes["name"]]["columns"] as $column) {
  265.                         $unique[$attributes["unique"]][] = $column["localColumnName"];
  266.                     }
  267.                 } else {
  268.                     $unique[$attributes["unique"]][] = ($attributes["externalAlias"] ?? $alias) . ucfirst($attributes["name"]);
  269.                 }
  270.             }
  271.             // Set the initial value o the ORM annotation
  272.             $aux "\t/**\n";
  273.             // Check if the property is part of the entity id
  274.             if ($attributes["id"]) { $aux .= "\t * @ORM\Id()\n"; }
  275.             if ($attributes["generated"]) { $aux .= "\t * @ORM\GeneratedValue()\n"; }
  276.             // When the property is another entity
  277.             if ($attributes["type"] == "entity") {
  278.                 // Get the columns that are part of the foreign key constraint
  279.                 $columns $entity["joins"][$attributes["name"]]["columns"];
  280.                 // Set the nullable attribute as a string
  281.                 $nullable $attributes["nullable"] ? "true" "false";
  282.                 // Get the target entity of the property
  283.                 $target substr($entity["joins"][$attributes["name"]]["targetEntity"], 03);
  284.                 // Add the many-to-one annotation
  285.                 $aux .= "\t * @ORM\\ManyToOne(targetEntity=\"$target\")\n";
  286.                 // Check how many database columns are part of the foreign key
  287.                 if (count($columns) == 1) {
  288.                     // Set the annotation when the foreign key has one column
  289.                     $aux .= "\t * @ORM\\JoinColumn(name=\"{$columns[0]["localColumnName"]}\", referencedColumnName=\"{$columns[0]["referencedColumnName"]}\", nullable=$nullable)\n";
  290.                 } else {
  291.                     unset($temp);
  292.                     // Set the annotation when the foreign key has multiple columns
  293.                     $aux .= "\t * @ORM\JoinColumns({\n";
  294.                     // Goes through the columns
  295.                     foreach ($columns as $column) {
  296.                         // Set the annotation for the column
  297.                         $temp[] = "\t\t * @ORM\\JoinColumn(name=\"{$column["localColumnName"]}\", referencedColumnName=\"{$column["referencedColumnName"]}\", nullable=$nullable)";
  298.                     }
  299.                     // Set the annotation in the result
  300.                     $aux .= (join(",\n"$temp ?? []) . "\n");
  301.                     // Set the join columns block edn
  302.                     $aux .= "\t * })\n";
  303.                 }
  304.             } else {
  305.                 unset($attr$columns);
  306.                 // Goes through the informed attributes
  307.                 foreach (array_filter($attributes) as $prop => $value) {
  308.                     // Ignore attribute that aren't used by doctrine ORM
  309.                     if (in_array($prop$attrToIgnore)) { continue; }
  310.                     // Set the database column name
  311.                     if ($prop == "name") { $value = ($attributes["externalAlias"] ?? $alias) . ucfirst($value); }
  312.                     // Convert the boolean values to string
  313.                     if (is_bool($value)) { $value $value "true" "false"; }
  314.                     // Attributes that need slashes
  315.                     if (in_array($prop, ["name""type"])) { $value "\"$value\""; }
  316.                     // Check if it is a collection
  317.                     if (is_array($value)) {
  318.                         // Used a custom function to get typed values considered as null by default
  319.                         $value array_filter($value, fn($item) => !is_null($item) && $item !== false);
  320.                         // If  the collection is empty, we don't need to save the property
  321.                         if (empty($value)) { continue; }
  322.                         // Encode the collection
  323.                         $value json_encode($value);
  324.                     }
  325.                     // Add the attribute to the temporary list
  326.                     $attr[] = "$prop=$value";
  327.                 }
  328.                 // Set the ORM column attributes
  329.                 $temp join(", "$attr ?? []);
  330.                 // Add the ORM column to the result
  331.                 $aux .= "\t * @ORM\Column($temp)\n";
  332.             }
  333.             // Set the property prefix
  334.             $propPrefix $attributes["type"] != "entity" "?" "";
  335.             // Set the property declaration
  336.             $properties[] = $aux "\t */\n\tprotected $propPrefix$type \${$attributes["name"]};\n";
  337.             // Check if the property has a default value
  338.             if (!is_null($attributes["options"]) && !is_null($attributes["options"]["default"])) {
  339.                 // Set the commentary part of the code
  340.                 $defaults[] = "\t\t//Set";
  341.                 // Set the verification and property assignment of the property in the "defaults" method
  342.                 $defaults[] = "\t\tif (is_null(\$this->get$ucfName())) { \$this->set$ucfName({$attributes["options"]["default"]}); }";
  343.             }
  344.             // Ignore the code for get and set methods for the properties "code" and "link" when it is a simple class
  345.             if ($extends == "SimpleEntity" && (in_array($attributes["name"], ["code""link"]))) { continue; }
  346.             // Set the get method for the property
  347.             $methods[] = "\tpublic function get$ucfName(): $propPrefix$type { return \$this->{$attributes["name"]}; }";
  348.             // Set the set method for the property
  349.             $methods[] = "\tpublic function set$ucfName($propPrefix$type \${$attributes["name"]}): self { \$this->{$attributes["name"]} = \${$attributes["name"]}; return \$this; }\n";
  350.         }
  351.         // Convert the properties from array to string
  352.         $properties join("\n"$properties ?? []);
  353.         // Convert the methods from array to string
  354.         $methods join("\n"$methods ?? []);
  355.         // Check if the entity has default values
  356.         if (isset($defaults)) {
  357.             // Convert the defaults values from array to string
  358.             $temp join("\n"$defaults);
  359.             // Set the annotation and code for the "default" method
  360.             $defaults "\t/**
  361. \t * @ORM\PrePersist
  362. \t */
  363. \tpublic function defaults()
  364. \t{
  365. $temp
  366. \t\t// Apply the remaining default
  367. \t\tparent::defaults();
  368. \t}\n";
  369.         } else {
  370.             // Used like this to simplify the code letter on
  371.             $defaults "";
  372.         }
  373.         // Check for unique constraint
  374.         if (isset($unique)) {
  375.             // Remove the previous values from the variable
  376.             unset($temp);
  377.             // Goes through the columns in the constraint
  378.             foreach ($unique as $name => $values) {
  379.                 // Add the quotation marks to the values
  380.                 $columns "{" join(", "array_map(fn($item) => "\"$item\""$values)) . "}";
  381.                 // Create the constraint annotation and add it to the list
  382.                 $temp[] = " *          @ORM\\UniqueConstraint(name=\"UN_{$alias}_$name\", columns=$columns)";
  383.             }
  384.             // Convert the list to a string joining the constraints
  385.             $constraints join(",\n"$temp ?? []);
  386.             // Create the table information annotation part with the constraints
  387.             $table " * @ORM\\Table(
  388.  *     name=\"$alias{$entity["name"]}\",
  389.  *     uniqueConstraints={
  390. $constraints
  391.  *      }
  392.  * )";
  393.         } else {
  394.             // Create the table information annotation part
  395.             $table " * @ORM\\Table(name=\"$alias{$entity["name"]}\")";
  396.         }
  397.         // Create the repository annotation part
  398.         $repository " * @ORM\\Entity(repositoryClass=\"App\\Repository\\$application\\{$alias}Repository\")";
  399.         // Create the annotation that comes before the class definition
  400.         if ($entity["logger"]) {
  401.             // When the entity have log columns
  402.             $orm "/**
  403. $repository
  404.  * @ORM\\EntityListeners({\"App\\Util\\WhoPerformedListener\"})
  405. $table
  406.  * @ORM\\HasLifecycleCallbacks
  407.  */";
  408.         } else {
  409.             // When the entity doesn't have log columns
  410.             $orm "/**
  411. $repository
  412. $table
  413.  */";
  414.         }
  415.         // Replace the production namespace for the development namespace
  416.         if ($development) { $orm str_replace($application$mappedNamespace$orm); }
  417.         // Return the entity code
  418.         return $development "<?php
  419. namespace App\\Entity\\$mappedNamespace;
  420. use App\\Entity\\$application\\$alias as Official;
  421. use Doctrine\ORM\Mapping as ORM;
  422. $orm
  423. class $alias extends Official {}
  424. "<?php
  425. namespace App\\Entity\\$application;
  426. use App\\Util\\$extends;
  427. use Doctrine\ORM\Mapping as ORM;
  428. $orm
  429. class $alias extends $extends
  430. {
  431.     public const TABLE_NAME = \"$alias{$entity["name"]}\";
  432. $properties
  433. $methods
  434. $defaults}
  435. ";
  436.     }
  437.     /**
  438.      * Generates the code for the class that represent the entity repository
  439.      * @param string $application used to define the class namespace
  440.      * @param string $alias the entity alias (in general, have 3 letters)
  441.      * @return string the code to be written in the file
  442.      */
  443.     private function generateRepository(string $applicationstring $alias): string
  444.     {
  445.         // Generate the repository code according the default model below
  446.         return "<?php
  447. namespace App\\Repository\\$application;
  448. use App\\Entity\\$application\\$alias;
  449. use Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository;
  450. use Doctrine\\Persistence\\ManagerRegistry;
  451. /**
  452.  * @method $alias|null find(\$id, \$lockMode = null, \$lockVersion = null)
  453.  * @method $alias|null findOneBy(array \$criteria, array \$orderBy = null)
  454.  * @method {$alias}[]    findAll()
  455.  * @method {$alias}[]    findBy(array \$criteria, array \$orderBy = null, \$limit = null, \$offset = null)
  456.  */
  457. class {$alias}Repository extends ServiceEntityRepository
  458. {
  459.     public function __construct(ManagerRegistry \$registry)
  460.     {
  461.         parent::__construct(\$registry, $alias::class);
  462.     }
  463. }
  464. ";
  465.     }
  466.     /**
  467.      * Generate the create table scripts for the entity
  468.      * @param array $entities the list with all entities information
  469.      * @param string $alias the entity alias (in general, have 3 letters)
  470.      * @param array $entity the entity properties with their attributes
  471.      * @param string $platform the database base platform
  472.      * @throws Exception since the type of the columns dependes on the platform, throws an exception when the type
  473.      * isn't defined in the MAP_TYPES constant for the selected platform
  474.      */
  475.     private function generateScript(array &$drops, array &$script, array &$afterstring $alias, array $entity, array $entitiesstring $platform)
  476.     {
  477.         $drops[] = "DROP TABLE IF EXISTS $alias{$entity["name"]};";
  478.         $result "CREATE TABLE $alias{$entity["name"]} (\n";
  479.         if ($entity["logger"]) {
  480.             foreach (self::LOGGER as $columns) {
  481.                 $column sprintf("%-24s"$columns[0]);
  482.                 $type sprintf("%-24s"$columns[1]);
  483.                 $nullable $columns[2] ? "    NULL" "NOT NULL";
  484.                 $temp[] = "\t$column$type$nullable";
  485.             }
  486.             $defaults[] = ["column" => "Register""value" => "GETDATE()"];
  487.             $defaults[] = ["column" => "Version""value" => "0"];
  488.         }
  489.         foreach ($entity["properties"] as $property) {
  490.             // Check if the property was removed
  491.             if (is_null($property["type"])) { continue; }
  492.             $columnName = ($property["externalAlias"] ?? $alias) . ucfirst($property["name"]);
  493.             $column sprintf("%-24s""$columnName");
  494.             $nullable $property["nullable"] ? "    NULL" "NOT NULL";
  495.             if ($property["id"]) {
  496.                 if ($property["type"] == "entity") {
  497.                     foreach ($entity["joins"][$property["name"]]["columns"] as $column) {
  498.                         $id[] = $column["localColumnName"];
  499.                     }
  500.                 } else {
  501.                     $id[] = $columnName;
  502.                 }
  503.             }
  504.             if ($property["unique"]) {
  505.                 if ($property["type"] == "entity") {
  506.                     foreach ($entity["joins"][$property["name"]]["columns"] as $column) {
  507.                         $unique[$property["unique"]][] = $column["localColumnName"];
  508.                     }
  509.                 } else {
  510.                     $unique[$property["unique"]][] = $columnName;
  511.                 }
  512.             }
  513.             if ($property["type"] == "entity") {
  514.                 $target substr($entity["joins"][$property["name"]]["targetEntity"], 03);
  515.                 foreach ($entity["joins"][$property["name"]]["columns"] as $column) {
  516.                     list($local$ref) = [[], []];
  517.                     $local[] = $column["localColumnName"];
  518.                     $ref[] = $column["referencedColumnName"];
  519.                     $search strtolower(substr($column["referencedColumnName"], 3));
  520.                     $filter = function($item) use ($search) { return $item["name"] == $search; };
  521.                     $aux array_values(array_filter($entities[$target]["properties"], $filter))[0];
  522.                     $type $this->getType($aux$platformtrue);
  523.                     $temp[] = sprintf("\t%-24s%-24s%s"$column["localColumnName"], $type$nullable);
  524.                     $aux = [
  525.                         "name" => "CONSTRAINT FK_{$alias}_to_$target",
  526.                         "target" => $entity["joins"][$property["name"]]["targetEntity"],
  527.                         "local" => join(", "$local),
  528.                         "ref" => join(", "$ref)
  529.                     ];
  530.                     if ($entity["order"] < $entities[$target]["order"]) {
  531.                         $f "ALTER TABLE %s%s ADD %s FOREIGN KEY (%s) REFERENCES %s (%s);";
  532.                         $after[] = sprintf($f$aliasucfirst($entity["name"]), $aux["name"], $aux["local"], $aux["target"], $aux["ref"]);
  533.                     } else {
  534.                         $foreignKeys[] = $aux;
  535.                     }
  536.                 }
  537.             } else {
  538.                 $type sprintf("%-24s"$this->getType($property$platform));
  539.                 $temp[] = "\t$column$type$nullable";
  540.             }
  541.             if (!is_null($property["options"]) && !is_null($property["options"]["default"])) {
  542.                 $value $property["options"]["default"];
  543.                 $defaults[] = [
  544.                     "column" => $columnName,
  545.                     "value" => is_string($value) ? "'$value'" $value
  546.                 ];
  547.             }
  548.         }
  549.         if (isset($id)) {
  550.             if (count($id) == 1) {
  551.                 $name "CONSTRAINT PK_{$alias}_$id[0]";
  552.                 $columns $id[0];
  553.             } else {
  554.                 $name "CONSTRAINT PK_{$alias}_MultiColumn";
  555.                 $columns join(", "$id);
  556.             }
  557.             $temp[] = sprintf("\t%-48sPRIMARY KEY (%s)"$name$columns);
  558.         }
  559.         if (isset($unique)) {
  560.             foreach ($unique as $name => $values) {
  561.                 $name "CONSTRAINT UN_{$alias}_$name";
  562.                 $columns join(", "$values);
  563.                 $temp[] = sprintf("\t%-48sUNIQUE (%s)"$name$columns);
  564.             }
  565.         }
  566.         if (isset($foreignKeys)) {
  567.             foreach ($foreignKeys as $fk) {
  568.                 $f "\t%-48sFOREIGN KEY (%s) REFERENCES %s (%s)";
  569.                 $temp[] = sprintf($f$fk["name"], $fk["local"], $fk["target"], $fk["ref"]);
  570.             }
  571.         }
  572.         if (isset($defaults)) {
  573.             foreach ($defaults as $df) {
  574.                 $name "CONSTRAINT DF_{$alias}_{$df["column"]}";
  575.                 $temp[] = sprintf("\t%-48sDEFAULT %s FOR %s"$name$df["value"], $df["column"]);
  576.             }
  577.         }
  578.         $result .= join(",\n"$temp ?? []);
  579.         $script[] = ($result "\n);\n");
  580.     }
  581.     private function getType(array $optionsint $platformbool $foreignKey=false): string
  582.     {
  583.         // Get only the options with values
  584.         $options array_filter($options);
  585.         // Get the doctrine type of the property
  586.         $type $options["type"];
  587.         // Get the platform type
  588.         $mapped self::MAP_TYPES[$type][$platform];
  589.         // Set the result
  590.         switch ($type) {
  591.             // Type that can be return direct without any detail
  592.             case "smallint":
  593.             case "integer":
  594.             case "bigint":
  595.             case "text":
  596.             case "date":
  597.             case "datetime":
  598.             case "time":
  599.                 $result $mapped;
  600.                 break;
  601.             // Types that can have precision and scale
  602.             case "decimal":
  603.             case "float":
  604.                 // Check if the precision and scale were informed
  605.                 if (isset($options["precision"], $options["scale"])) {
  606.                     // Get the precision and scale of the property
  607.                     list($precision$scale) = [$options["precision"], $options["scale"]];
  608.                     // Set the result with the information
  609.                     $result "$mapped($precision$scale)";
  610.                 } else {
  611.                     // No additional information provided
  612.                     $result $mapped;
  613.                 }
  614.                 break;
  615.             // Type that have length
  616.             case "string":
  617.                 $result "{$mapped[$options["options"]["fixed"]]}({$options["length"]})";
  618.                 break;
  619.             default:
  620.                 throw new Exception("Type not implement");
  621.         }
  622.         if (isset($options["generated"]) && !$foreignKey) {
  623.             $result = match ($platform) {
  624.                 self::SQLServer => "$result IDENTITY(1, 1)",
  625.                 default  => throw new Exception("Incremental strategy not implement to the vendor \"$platform\"")
  626.             };
  627.         }
  628.         return $result;
  629.     }
  630.     private function writeFile($content$alias$type$namespace)
  631.     {
  632.         // Set the project directory
  633.         $project $this->getParameter("kernel.project_dir");
  634.         // Set the temp directory
  635.         $tempType join("/", [$projectself::DOC_ENTITIES_TEMP$type]);
  636.         // Set the type directory
  637.         $tempNamespace join("/", [$projectself::DOC_ENTITIES_TEMP$type$namespace]);
  638.         // Create the temp directory if it doesn't exist
  639.         if (!file_exists($tempType)) { mkdir($tempType0775); }
  640.         // Create the type directory if it doesn't exist
  641.         if (!file_exists($tempNamespace)) { mkdir($tempNamespace0775); }
  642.         // Set the file name
  643.         $fileName $type == "Entity" "$tempNamespace/$alias.php" :  "$tempNamespace/$alias$type.php";
  644.         // Write the content in the file
  645.         file_put_contents($fileName$content);
  646.     }
  647. }