Files
Starseed/migrations/Version20260615150000.php
T
Matthieu d9313dbec8 feat(transport) : schéma + entités Carrier + contrat lecture (ERP-155/157)
Schéma BDD du répertoire transporteurs (M4) + entités + contrat de lecture
(liste + détail), socle du front.

- Migration Version20260615150000 : tables carrier / carrier_address /
  carrier_contact / carrier_price (FK cross-module, CHECK enum, index partiel
  uq_carrier_name_active, COMMENT ON COLUMN). uploaded_document et
  qualimat_carrier réutilisées (non recréées).
- Entités Carrier* (#[Auditable], Timestampable/Blamable) + ApiResource
  LECTURE seule (GetCollection + Get via CarrierProvider, anti-N+1, exclusion
  archivés + ?includeArchived). Écriture (POST/PATCH + Processor) reportée WT4+.
- QualimatCarrier : mapping ORM lecture seule sur la table référentielle
  existante (sortie du schema_filter, mapping aligné DDL ERP-39, schema:update
  no-op) + endpoint de recherche read-only (§ 4.7).
- Relations cross-module des prix (Client/Supplier/adresses) via contrats
  Shared (ClientInterface, SupplierInterface, ClientAddressInterface,
  SupplierAddressInterface) + resolve_target_entities — sans import inter-module
  (règle n°1). Ajout du groupe supplier_address:read aux champs de
  SupplierAddress pour l'embed.
- Garde-fous : ColumnCommentsCatalog (carrier* + qualimat_carrier), makefile
  test-db-setup (index partiel carrier), i18n audit (transport_carrier*),
  EntitiesAreTimestampableBlamableTest (QualimatCarrier whitelisté).
- CarrierSerializationContractTest : contrat JSON liste + détail vérifié
  (embeds objet, booléens, enveloppe Hydra) ; JSON réel capturé dans
  spec-back § 4.0.bis.

make db-reset OK, make test vert (731), make nuxt-test vert (480),
php-cs-fixer OK.
2026-06-15 19:15:12 +02:00

357 lines
23 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Shared\Infrastructure\Database\ColumnCommentsCatalog;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* M4 — Repertoire transporteurs (ERP-155/157) : creation du schema BDD du
* repertoire transporteurs sous le module Transport (jumeau des M2/M3).
*
* Tables creees :
* - carrier : table principale (formulaire + lien QUALIMAT + archive + soft-delete
* + Timestampable/Blamable) ;
* - carrier_address / carrier_contact / carrier_price : sous-collections 1:n.
*
* Tables NON recrees (reutilisees) :
* - qualimat_carrier (ERP-39, Version20260612150000) : cible de la FK editable
* carrier.qualimat_carrier_id (§ 2.5) ;
* - uploaded_document (ERP-154, Version20260615130000) : cible de la FK
* carrier.discharge_document_id (Decharge, § 2.7) ;
* - client / client_address / supplier / supplier_address (M1/M2) et site (Sites) :
* cibles des FK de carrier_price (onglet Prix, RG-4.10/4.11).
*
* Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire :
* FK cross-module (user, client, client_address, supplier, supplier_address, site,
* qualimat_carrier, uploaded_document). Le tri par timestamp au sein du namespace
* racine garantit l'ordre apres la creation de ces tables sur base vide.
*
* Decision IDs (spec § 2.2, tranchee a ce ticket) : carrier et ses sous-tables
* utilisent `INT GENERATED BY DEFAULT AS IDENTITY` (homogeneite globale Starseed
* M1/M2/M3, evite la friction bigint->string de l'ORM). Seule
* carrier.qualimat_carrier_id est BIGINT pour matcher qualimat_carrier.id (existant).
* Horodatages `TIMESTAMP(0) WITHOUT TIME ZONE` (le TimestampableBlamableTrait mappe
* `datetime_immutable`), pour que `schema:update --force` reste un no-op.
*
* Chaque colonne porte son `COMMENT ON COLUMN` (regle ABSOLUE n°12). Les 4
* tables carrier* etant mappees par l'ORM des ce ticket, elles sont aussi ajoutees
* a ColumnCommentsCatalog : `app:apply-column-comments` (test-db-setup) rejoue ces
* COMMENT apres le `schema:update --force` qui les droperait sinon.
*/
final class Version20260615150000 extends AbstractMigration
{
public function getDescription(): string
{
return 'ERP-155/157 (M4) : tables carrier + carrier_address + carrier_contact + carrier_price (repertoire transporteurs).';
}
public function up(Schema $schema): void
{
$this->createCarrierTable();
$this->createCarrierAddress();
$this->createCarrierContact();
$this->createCarrierPrice();
}
public function down(Schema $schema): void
{
// Ordre inverse des dependances FK : sous-collections d'abord, puis carrier.
$this->addSql('DROP TABLE IF EXISTS carrier_price');
$this->addSql('DROP TABLE IF EXISTS carrier_contact');
$this->addSql('DROP TABLE IF EXISTS carrier_address');
$this->addSql('DROP TABLE IF EXISTS carrier');
}
// =================================================================
// Table principale `carrier`
// =================================================================
private function createCarrierTable(): void
{
$this->addSql(<<<'SQL'
CREATE TABLE carrier (
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
qualimat_carrier_id BIGINT DEFAULT NULL,
name VARCHAR(255) NOT NULL,
certification_type VARCHAR(20) DEFAULT NULL,
is_chartered BOOLEAN DEFAULT FALSE NOT NULL,
indexation_rate NUMERIC(5, 2) DEFAULT NULL,
container_type VARCHAR(12) DEFAULT NULL,
volume_m3 NUMERIC(10, 2) DEFAULT NULL,
discharge_document_id INT DEFAULT NULL,
liot_plates TEXT DEFAULT NULL,
is_archived BOOLEAN DEFAULT FALSE NOT NULL,
archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
deleted_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
created_by INT DEFAULT NULL,
updated_by INT DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT chk_carrier_certification_type
CHECK (certification_type IS NULL OR certification_type IN ('QUALIMAT', 'GMP_PLUS', 'OVOCOM', 'COMPTE_PROPRE', 'AUTRE')),
CONSTRAINT chk_carrier_container_type
CHECK (container_type IS NULL OR container_type IN ('BENNE', 'FOND_MOUVANT')),
CONSTRAINT fk_carrier_qualimat
FOREIGN KEY (qualimat_carrier_id) REFERENCES qualimat_carrier (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_discharge_document
FOREIGN KEY (discharge_document_id) REFERENCES uploaded_document (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_created_by
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_updated_by
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
)
SQL);
$this->addSql('CREATE INDEX idx_carrier_is_archived ON carrier (is_archived)');
$this->addSql('CREATE INDEX idx_carrier_deleted_at ON carrier (deleted_at)');
$this->addSql('CREATE INDEX idx_carrier_qualimat ON carrier (qualimat_carrier_id)');
$this->addSql('CREATE INDEX idx_carrier_discharge_document ON carrier (discharge_document_id)');
$this->addSql('CREATE INDEX idx_carrier_created_by ON carrier (created_by)');
$this->addSql('CREATE INDEX idx_carrier_updated_by ON carrier (updated_by)');
// Unicite metier partielle : nom insensible a la casse, parmi les
// non-archives ET non soft-deletes uniquement (§ 2.6). Inexprimable en ORM.
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX uq_carrier_name_active
ON carrier (LOWER(name))
WHERE is_archived = FALSE AND deleted_at IS NULL
SQL);
$this->comment('carrier', '_table', 'Repertoire transporteurs (M4 Transport) — entites editables, archivables (is_archived) et soft-deletables (deleted_at). Distinct du referentiel qualimat_carrier.');
$this->comment('carrier', 'id', 'Identifiant interne auto-incremente.');
$this->comment('carrier', 'qualimat_carrier_id', 'Lien editable vers le referentiel QUALIMAT (saisie assistee RG-4.01). FK -> qualimat_carrier.id, ON DELETE SET NULL : transporteur conserve si la ligne QUALIMAT disparait.');
$this->comment('carrier', 'name', 'Raison sociale du transporteur (stockee en MAJUSCULES). Unique case-insensitive parmi les non-archives/non-supprimes (uq_carrier_name_active, RG-4.12 / § 2.6).');
$this->comment('carrier', 'certification_type', 'Type de certification : QUALIMAT (si lie, lecture seule) ou GMP_PLUS/OVOCOM/COMPTE_PROPRE/AUTRE. AUTRE declenche le champ Decharge (RG-4.02). Null en cas LIOT (RG-4.01).');
$this->comment('carrier', 'is_chartered', '« Affreter » coche : declenche indexation/benne-fond mouvant/volume, obligatoires (RG-4.03). Faux par defaut.');
$this->comment('carrier', 'indexation_rate', 'Taux d indexation en pourcentage (NUMERIC 5,2) — renseigne si affrete (RG-4.03).');
$this->comment('carrier', 'container_type', 'Type de contenant BENNE|FOND_MOUVANT (chk_carrier_container_type) — renseigne si affrete (RG-4.03).');
$this->comment('carrier', 'volume_m3', 'Volume en m3 (NUMERIC 10,2) — renseigne si affrete (RG-4.03).');
$this->comment('carrier', 'discharge_document_id', 'Document de Decharge (visible si certification_type = AUTRE, RG-4.02). FK -> uploaded_document.id (infra Shared § 2.7), ON DELETE SET NULL.');
$this->comment('carrier', 'liot_plates', 'Immatriculations LIOT separees par « ; » (cas special nom=LIOT, RG-4.01). Les autres champs sont masques dans ce cas.');
$this->comment('carrier', 'is_archived', 'Drapeau fonctionnel d archivage — masque par defaut dans la liste. Bascule via permission transport.carriers.archive (Admin seul).');
$this->comment('carrier', 'archived_at', 'Horodatage de l archivage — pose quand is_archived passe a vrai, remis a null a la restauration.');
$this->comment('carrier', 'deleted_at', 'Horodatage du soft-delete technique — non expose par l API au M4. Null = ligne active.');
$this->addTimestampableBlamableComments('carrier');
}
// =================================================================
// Sous-collection : adresses (1:n)
// =================================================================
private function createCarrierAddress(): void
{
$this->addSql(<<<'SQL'
CREATE TABLE carrier_address (
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
carrier_id INT NOT NULL,
country VARCHAR(80) DEFAULT 'France' NOT NULL,
postal_code VARCHAR(20) DEFAULT NULL,
city VARCHAR(120) DEFAULT NULL,
street VARCHAR(255) DEFAULT NULL,
street_complement VARCHAR(255) DEFAULT NULL,
position INT DEFAULT 0 NOT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
created_by INT DEFAULT NULL,
updated_by INT DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_carrier_address_carrier
FOREIGN KEY (carrier_id) REFERENCES carrier (id) ON DELETE CASCADE,
CONSTRAINT fk_carrier_address_created_by
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_address_updated_by
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
)
SQL);
$this->addSql('CREATE INDEX idx_carrier_address_carrier ON carrier_address (carrier_id)');
$this->addSql('CREATE INDEX idx_carrier_address_created_by ON carrier_address (created_by)');
$this->addSql('CREATE INDEX idx_carrier_address_updated_by ON carrier_address (updated_by)');
$this->comment('carrier_address', '_table', 'Adresses d un transporteur (1:n) — onglet Adresse (M4). Pre-remplie depuis QUALIMAT si applicable (RG-4.05).');
$this->comment('carrier_address', 'id', 'Identifiant interne auto-incremente.');
$this->comment('carrier_address', 'carrier_id', 'FK -> carrier.id, ON DELETE CASCADE — transporteur proprietaire de l adresse.');
$this->comment('carrier_address', 'country', 'Pays de l adresse — defaut France.');
$this->comment('carrier_address', 'postal_code', 'Code postal (saisie assistee BAN cote front, RG-4.06).');
$this->comment('carrier_address', 'city', 'Ville — preremplie depuis le code postal via API BAN cote front.');
$this->comment('carrier_address', 'street', 'Numero et voie de l adresse.');
$this->comment('carrier_address', 'street_complement', 'Complement d adresse (etage, batiment...) — optionnel.');
$this->comment('carrier_address', 'position', 'Ordre d affichage de l adresse dans la liste du transporteur (croissant).');
$this->addTimestampableBlamableComments('carrier_address');
}
// =================================================================
// Sous-collection : contacts (1:n)
// =================================================================
private function createCarrierContact(): void
{
$this->addSql(<<<'SQL'
CREATE TABLE carrier_contact (
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
carrier_id INT NOT NULL,
first_name VARCHAR(120) DEFAULT NULL,
last_name VARCHAR(120) DEFAULT NULL,
job_title VARCHAR(120) DEFAULT NULL,
phone_primary VARCHAR(20) DEFAULT NULL,
phone_secondary VARCHAR(20) DEFAULT NULL,
email VARCHAR(180) DEFAULT NULL,
position INT DEFAULT 0 NOT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
created_by INT DEFAULT NULL,
updated_by INT DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT chk_carrier_contact_filled
CHECK (first_name IS NOT NULL OR last_name IS NOT NULL OR job_title IS NOT NULL
OR phone_primary IS NOT NULL OR email IS NOT NULL),
CONSTRAINT fk_carrier_contact_carrier
FOREIGN KEY (carrier_id) REFERENCES carrier (id) ON DELETE CASCADE,
CONSTRAINT fk_carrier_contact_created_by
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_contact_updated_by
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
)
SQL);
$this->addSql('CREATE INDEX idx_carrier_contact_carrier ON carrier_contact (carrier_id)');
$this->addSql('CREATE INDEX idx_carrier_contact_created_by ON carrier_contact (created_by)');
$this->addSql('CREATE INDEX idx_carrier_contact_updated_by ON carrier_contact (updated_by)');
$this->comment('carrier_contact', '_table', 'Contacts d un transporteur (1:n) — onglet Contact (M4). Au moins un champ rempli (RG-4.08, chk_carrier_contact_filled), max 2 telephones.');
$this->comment('carrier_contact', 'id', 'Identifiant interne auto-incremente.');
$this->comment('carrier_contact', 'carrier_id', 'FK -> carrier.id, ON DELETE CASCADE — transporteur proprietaire du contact.');
$this->comment('carrier_contact', 'first_name', 'Prenom du contact (capitalise serveur). Au moins un champ du contact est requis (RG-4.08).');
$this->comment('carrier_contact', 'last_name', 'Nom du contact (capitalise serveur). Au moins un champ du contact est requis (RG-4.08).');
$this->comment('carrier_contact', 'job_title', 'Fonction / intitule de poste du contact (≤ 120 caracteres).');
$this->comment('carrier_contact', 'phone_primary', 'Telephone principal — chiffres uniquement (normalisation serveur).');
$this->comment('carrier_contact', 'phone_secondary', 'Telephone secondaire — chiffres uniquement (max 2 telephones, RG-4.08).');
$this->comment('carrier_contact', 'email', 'Email du contact (lowercase serveur).');
$this->comment('carrier_contact', 'position', 'Ordre d affichage du contact dans la liste du transporteur (croissant).');
$this->addTimestampableBlamableComments('carrier_contact');
}
// =================================================================
// Sous-collection : prix (1:n) — onglet Prix (RG-4.09 -> RG-4.11)
// =================================================================
private function createCarrierPrice(): void
{
$this->addSql(<<<'SQL'
CREATE TABLE carrier_price (
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
carrier_id INT NOT NULL,
direction VARCHAR(12) NOT NULL,
client_id INT DEFAULT NULL,
client_delivery_address_id INT DEFAULT NULL,
departure_site_id INT DEFAULT NULL,
supplier_id INT DEFAULT NULL,
supplier_supply_address_id INT DEFAULT NULL,
delivery_site_id INT DEFAULT NULL,
container_type VARCHAR(12) NOT NULL,
pricing_unit VARCHAR(8) NOT NULL,
price NUMERIC(12, 2) NOT NULL,
price_state VARCHAR(12) NOT NULL,
position INT DEFAULT 0 NOT NULL,
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
created_by INT DEFAULT NULL,
updated_by INT DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT chk_carrier_price_direction CHECK (direction IN ('CLIENT', 'FOURNISSEUR')),
CONSTRAINT chk_carrier_price_container CHECK (container_type IN ('BENNE', 'FOND_MOUVANT')),
CONSTRAINT chk_carrier_price_unit CHECK (pricing_unit IN ('FORFAIT', 'TONNE')),
CONSTRAINT chk_carrier_price_state CHECK (price_state IN ('EN_COURS', 'VALIDE', 'NON_VALIDE')),
CONSTRAINT chk_carrier_price_client_branch
CHECK (direction <> 'CLIENT' OR (client_id IS NOT NULL AND supplier_id IS NULL)),
CONSTRAINT chk_carrier_price_supplier_branch
CHECK (direction <> 'FOURNISSEUR' OR (supplier_id IS NOT NULL AND client_id IS NULL)),
CONSTRAINT fk_carrier_price_carrier
FOREIGN KEY (carrier_id) REFERENCES carrier (id) ON DELETE CASCADE,
CONSTRAINT fk_carrier_price_client
FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_client_address
FOREIGN KEY (client_delivery_address_id) REFERENCES client_address (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_departure_site
FOREIGN KEY (departure_site_id) REFERENCES site (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_supplier
FOREIGN KEY (supplier_id) REFERENCES supplier (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_supplier_address
FOREIGN KEY (supplier_supply_address_id) REFERENCES supplier_address (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_delivery_site
FOREIGN KEY (delivery_site_id) REFERENCES site (id) ON DELETE RESTRICT,
CONSTRAINT fk_carrier_price_created_by
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL,
CONSTRAINT fk_carrier_price_updated_by
FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL
)
SQL);
$this->addSql('CREATE INDEX idx_carrier_price_carrier ON carrier_price (carrier_id)');
$this->addSql('CREATE INDEX idx_carrier_price_client ON carrier_price (client_id)');
$this->addSql('CREATE INDEX idx_carrier_price_client_address ON carrier_price (client_delivery_address_id)');
$this->addSql('CREATE INDEX idx_carrier_price_departure_site ON carrier_price (departure_site_id)');
$this->addSql('CREATE INDEX idx_carrier_price_supplier ON carrier_price (supplier_id)');
$this->addSql('CREATE INDEX idx_carrier_price_supplier_address ON carrier_price (supplier_supply_address_id)');
$this->addSql('CREATE INDEX idx_carrier_price_delivery_site ON carrier_price (delivery_site_id)');
$this->addSql('CREATE INDEX idx_carrier_price_created_by ON carrier_price (created_by)');
$this->addSql('CREATE INDEX idx_carrier_price_updated_by ON carrier_price (updated_by)');
$this->comment('carrier_price', '_table', 'Prix d un transporteur (1:n) — onglet Prix (M4). Branche CLIENT ou FOURNISSEUR selon direction (RG-4.09→4.11, CHECK chk_carrier_price_*).');
$this->comment('carrier_price', 'id', 'Identifiant interne auto-incremente.');
$this->comment('carrier_price', 'carrier_id', 'FK -> carrier.id, ON DELETE CASCADE — transporteur proprietaire du prix.');
$this->comment('carrier_price', 'direction', 'Sens du prix : CLIENT ou FOURNISSEUR (RG-4.09). Pilote l affichage et l obligation des colonnes client_*/supplier_* (RG-4.10/4.11).');
$this->comment('carrier_price', 'client_id', 'Branche CLIENT (RG-4.10) : client concerne. FK -> client.id, ON DELETE RESTRICT. Requis ssi direction = CLIENT.');
$this->comment('carrier_price', 'client_delivery_address_id', 'Branche CLIENT : adresse de livraison du client. FK -> client_address.id, ON DELETE RESTRICT.');
$this->comment('carrier_price', 'departure_site_id', 'Branche CLIENT : adresse de depart = un des 3 sites (86/17/82). FK -> site.id, ON DELETE RESTRICT.');
$this->comment('carrier_price', 'supplier_id', 'Branche FOURNISSEUR (RG-4.11) : fournisseur concerne. FK -> supplier.id, ON DELETE RESTRICT. Requis ssi direction = FOURNISSEUR.');
$this->comment('carrier_price', 'supplier_supply_address_id', 'Branche FOURNISSEUR : adresse d approvisionnement du fournisseur. FK -> supplier_address.id, ON DELETE RESTRICT.');
$this->comment('carrier_price', 'delivery_site_id', 'Branche FOURNISSEUR : adresse de livraison = un des 3 sites (86/17/82). FK -> site.id, ON DELETE RESTRICT.');
$this->comment('carrier_price', 'container_type', 'Type de contenant BENNE|FOND_MOUVANT (chk_carrier_price_container).');
$this->comment('carrier_price', 'pricing_unit', 'Unite de tarification FORFAIT|TONNE (chk_carrier_price_unit).');
$this->comment('carrier_price', 'price', 'Montant du prix (NUMERIC 12,2).');
$this->comment('carrier_price', 'price_state', 'Etat du prix : EN_COURS, VALIDE ou NON_VALIDE (chk_carrier_price_state). Affiche dans le tableau Prix.');
$this->comment('carrier_price', 'position', 'Ordre d affichage du prix dans la liste du transporteur (croissant).');
$this->addTimestampableBlamableComments('carrier_price');
}
// =================================================================
// Helpers (identiques au M2 Version20260605130000)
// =================================================================
/**
* Pose les 4 commentaires standardises Timestampable/Blamable sur une table,
* en reutilisant le catalogue partage (source unique, ERP-67).
*/
private function addTimestampableBlamableComments(string $table): void
{
foreach (ColumnCommentsCatalog::timestampableBlamableComments() as $column => $description) {
$this->comment($table, $column, $description);
}
}
/**
* Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou `COMMENT ON COLUMN`
* en dollar-quoting Postgres ($_$...$_$) pour eviter tout echappement d apostrophe.
*/
private function comment(string $table, string $column, string $description): void
{
$quotedTable = '"'.str_replace('"', '""', $table).'"';
if ('_table' === $column) {
$this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description));
return;
}
$this->addSql(sprintf(
'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
$quotedTable,
'"'.str_replace('"', '""', $column).'"',
$description,
));
}
}