Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Bonjour à tous,
Voilà ce que me renvois Zend comme erreur :
Exception information: Message: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`xxxxxxx`.`Logements`, CONSTRAINT `fk_Logement_Entreprise1` FOREIGN KEY (`Entreprises_id`) REFERENCES `Entreprises` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION) Stack trace: #0 /var/www/xxxxxx/library/Zend/Db/Statement.php(300): Zend_Db_Statement_Pdo->_execute(Array) #1 /var/www/xxxxxx/library/Zend/Db/Adapter/Abstract.php(468): Zend_Db_Statement->execute(Array) #2 /var/www/xxxxxx/library/Zend/Db/Adapter/Pdo/Abstract.php(238): Zend_Db_Adapter_Abstract->query('UPDATE `Logemen...', Array) #3 /var/www/xxxxxx/library/Zend/Db/Adapter/Abstract.php(604): Zend_Db_Adapter_Pdo_Abstract->query('UPDATE `Logemen...', Array) #4 /var/www/xxxxxx/library/Zend/Db/Table/Abstract.php(1113): Zend_Db_Adapter_Abstract->update('Logements', Array, 'id = 1') #5 /var/www/xxxxxx/application/models/DbTable/Logements.php(75): Zend_Db_Table_Abstract->update(Array, 'id = 1') #6 /var/www/xxxxxx/application/controllers/LogementsController.php(83): Application_Model_DbTable_logements->updateLogements(1, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, Array, '1') #7 /var/www/xxxxxx/library/Zend/Controller/Action.php(513): LogementsController->editAction() #8 /var/www/xxxxxx/library/Zend/Controller/Dispatcher/Standard.php(289): Zend_Controller_Action->dispatch('editAction') #9 /var/www/xxxxxx/library/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http)) #10 /var/www/xxxxxx/library/Zend/Application/Bootstrap/Bootstrap.php(97): Zend_Controller_Front->dispatch() #11 /var/www/xxxxxx/library/Zend/Application.php(366): Zend_Application_Bootstrap_Bootstrap->run() #12 /var/www/xxxxxx/public/index.php(26): Zend_Application->run() #13 {main} Request Parameters: array ( 'controller' => 'Logements', 'action' => 'edit', 'id' => '1', 'module' => 'default', 'nom' => 'Logement', 'type' => 'Appartement', 'surface' => '', 'nbchambre' => '', 'nbpersonne' => '', 'adresse' => '', 'codepostal' => '', 'ville' => '', 'telephonefix' => '123456789', 'internet' => '0', 'piscine' => '0', 'vue' => '0', 'distancemer' => '', 'url' => '', 'Entreprises_id' => '1', 'Personnes_id' => '1', 'actif' => '1', 'submit' => 'Enregistrer', )
J'ai vérifié ma table dans ma base, tout me semble correct :
CREATE TABLE IF NOT EXISTS `xxxxxxx`.`Logements` ( `id` INT NOT NULL AUTO_INCREMENT , `nom` VARCHAR(50) NOT NULL , `type` VARCHAR(45) NULL , `surface` FLOAT NULL , `nbchambre` INT NULL , `nbpersonne` INT NULL , `adresse` VARCHAR(255) NULL , `codepostal` VARCHAR(7) NULL , `ville` VARCHAR(45) NULL , `telephonefix` VARCHAR(10) NULL , `internet` CHAR(1) NULL , `piscine` CHAR(1) NULL , `vue` CHAR(1) NULL , `distancemer` INT NULL , `url` VARCHAR(255) NULL , `Entreprises_id` INT NOT NULL , `Personnes_id` INT NOT NULL , `actif` TINYINT(4) NULL , PRIMARY KEY (`id`, `Entreprises_id`, `Personnes_id`) , INDEX `fk_Logement_Entreprise1` (`Entreprises_id` ASC) , INDEX `fk_Logement_Personne1` (`Personnes_id` ASC) , CONSTRAINT `fk_Logement_Entreprise1` FOREIGN KEY (`Entreprises_id` ) REFERENCES `xxxxxxx`.`Entreprises` (`id` ) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_Logement_Personne1` FOREIGN KEY (`Personnes_id` ) REFERENCES `xxxxxxx`.`Personnes` (`id` ) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB;
Que faut-il que je regarde d'autre ?
Et autre question, dans mon message d'erreur, d'où vient le "Enregistrer" que me sort Submit.
Si vous voyez le probleme,
Merci davance
Dernière modification par francoisA (23-07-2010 23:34:19)
Hors ligne
J'ai le problème en insert et update !
Hors ligne
Crées d'abord l'entreprise avec un id de 1
Hors ligne
L'Entreprise et la personne sont créées. Je les selectionne dans un menu déroulant qu'il génère en interrogeant la base de données...
Non vraiment, je ne comprends pas... il doit avoir un probleme dans mon code mais je ne trouve pas l'erreur !
Dernière modification par francoisA (08-07-2010 20:02:35)
Hors ligne
Il nous faudrait plutot le code de ton formulaire et de l'action appelé au submit
ps : le 'enregistrer' à priori c'est la valeur de ton submit dans ton form!
Hors ligne
Mon formulaire :
class Application_Form_Logements extends Zend_Form { public function init() { $this->setName('Logements'); $id = new Zend_Form_Element_Hidden('id'); $id->addFilter('Int'); $nom = new Zend_Form_Element_Text("nom", array('size'=>'20')); $nom->setLabel('Nom') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty') ->addValidator('StringLength', false, 0,50); $type = new Zend_Form_Element_Select('type'); $type->setLabel('type') ->addFilter('StripTags') ->addFilter('StringTrim') ->addMultiOptions(array('Appartement'=>'Appartement', 'Maison'=> 'Maison','Hotel'=>'Hotel')); $surface = new Zend_Form_Element_Text('surface', array('size'=>'5')); $surface->setLabel('surface') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('Float'); $nbchambre = new Zend_Form_Element_Text('nbchambre', array('size'=>'5')); $nbchambre->setLabel('nbchambre') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('Int'); $nbpersonne = new Zend_Form_Element_Text('nbpersonne',array('size'=>'5')); $nbpersonne->setLabel('nbpersonne') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('Int'); $adresse = new Zend_Form_Element_Text('adresse', array('size'=>'25')); $adresse->setLabel('Adresse') ->addFilter('StripTags') ->addFilter('StringTrim'); $ville = new Zend_Form_Element_Text('ville', array('size'=>'25')); $ville->setLabel('Ville') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('StringLength', false, 0,25); $codepostal = new Zend_Form_Element_Text('codepostal', array('size'=>'7')); $codepostal->setLabel('Code Postal') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('StringLength', false, 0,7); $telephonefix = new Zend_Form_Element_Text('telephonefix', array('size'=>'10')); $telephonefix->setLabel('Telephone Fix') ->setDescription("NE PAS mettre d'espace, de tiret,... entre les chiffres") //->setDescription("NE PAS mettre l'indicatif du pays") ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('StringLength', false, 9,10) ->setValue('0241555555') ->addValidator('Digits'); $internet = new Zend_Form_Element_Checkbox('internet'); $internet->setLabel('internet') ->addFilter('StripTags') ->addFilter('StringTrim'); $piscine = new Zend_Form_Element_Checkbox('piscine'); $piscine->setLabel('piscine') ->addFilter('StripTags') ->addFilter('StringTrim'); $vue = new Zend_Form_Element_Checkbox('vue'); $vue->setLabel('vue') ->addFilter('StripTags') ->addFilter('StringTrim'); $distancemer = new Zend_Form_Element_Text('distancemer', array('size'=>'5')); $distancemer->setLabel('distancemer') ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('Int'); $url = new Zend_Form_Element_Text('url', array('size'=>'70')); $url->setLabel('Adresse du site Web') ->addFilter('StripTags') ->addFilter('StringTrim') ->setValue("http://www.") ->addValidator('StringLength', false, 0,255); $Entreprises_id = new Zend_Form_Element_Select('Entreprises_id'); $Entreprises_id->setLabel('Entreprises_id') ->addFilter('Int'); // Menu Déroulant $Entreprises = new Application_Model_DbTable_Entreprises(); foreach ($Entreprises->fetchAll() as $c) { $Entreprises_id->addMultiOption($c->id, $c->nom); } $Personnes_id = new Zend_Form_Element_Select('Personnes_id'); $Personnes_id->addFilter('Int') ->setLabel('Personnes_id'); // Menu Déroulant $Personnes = new Application_Model_DbTable_Personnes(); foreach ($Personnes->fetchAll() as $c) { $Personnes_id->addMultiOption($c->id, $c->nom); } // if( !empty($_FILES['file']) ) { // $picture_temp = $_FILES['file']['tmp_name']; // $picture = $_FILES['file']['name']; // move_uploaded_file($picture_temp,$_Server['DOCUMENT_ROOT'].'/xxxxxx/upload/'.$picture); // } //Upload de l'image // $image = new Zend_Form_Element_File("image"); // $image ->setLabel('image') // ->setDestination('/var/www/xxxxxx/upload/') // ->setValueDisabled(true) // ->addValidator('Count', false, 1) // ->addValidator('Size',false, 102400) // ->addValidator('Extension', false, 'jpg,png'); $actif = new Zend_Form_Element_Checkbox('actif'); $actif->setLabel('Désactivé') ->addFilter('StripTags') ->addFilter('StringTrim'); $submit = new Zend_Form_Element_Submit('submit'); $submit->setAttrib('id', 'submitbutton'); $this->addElements(array($id,$nom, $type, $surface, $nbchambre,$nbpersonne, $adresse, $codepostal,$ville, $telephonefix,$internet,$piscine,$vue,$distancemer, $url, $Entreprises_id, $Personnes_id, $actif, $submit)); }
mon CRUD :
class Application_Model_DbTable_logements extends Zend_Db_Table_Abstract { protected $_name = 'Logements'; /** * * @param Int $id * @return ArrayOfLogements */ public function getLogements($id) { $id = (int)$id; $row = $this->fetchRow('id = '.$id); if (!$row) { throw new Exception("Impossible de trouver la ligne $id"); } return $row->toArray(); } public function addLogements($nom, $type, $surface, $nbchambre,$nbpersonne, $adresse, $codepostal,$ville, $telephonefix,$internet,$piscine,$vue,$distancemer, $url, $Entreprises_id, $Personnes_id, $actif) { $data = array( 'nom' => $nom, 'type' => $type, 'surface'=> $surface, 'nbchambre'=>$nbchambre, 'nbpersonne'=>$nbpersonne, 'adresse'=>$adresse, 'ville'=>$ville, 'codepostal'=>$codepostal, 'telephonefix'=>$telephonefix, 'internet'=> $internet, 'piscine'=> $piscine, 'vue'=>$vue, 'distancemer'=>$distancemer, 'url'=>$url, 'Entreprises_id'=>$Entreprises_id, 'Personnes_id'=>$Personnes_id, 'actif'=>$actif, ); $this->insert($data); } public function updateLogements($id,$nom, $type, $surface, $nbchambre,$nbpersonne, $adresse, $codepostal,$ville, $telephonefix,$internet,$piscine,$vue,$distancemer, $url, $Entreprise_id, $Personnes_id, $actif) { $data = array( 'id'=>$id, 'nom' => $nom, 'type' => $type, 'surface'=> $surface, 'nbchambre'=>$nbchambre, 'nbpersonne'=>$nbpersonne, 'adresse'=>$adresse, 'ville'=>$ville, 'codepostal'=>$codepostal, 'telephonefix'=>$telephonefix, 'internet'=> $internet, 'piscine'=> $piscine, 'vue'=>$vue, 'distancemer'=>$distancemer, 'url'=>$url, 'Entreprises_id'=>$Entreprises_id, 'Personnes_id'=>$Personnes_id, 'actif'=>$actif, ); $this->update($data, 'id = '.(int)$id); } /** * * @param int $id * @param int $actif */ public function desactiveLogements($id, $actif) { $data = array( 'id'=> $id, 'actif'=>$actif, ); $this->update($data, 'id = '.(int)$id); } /** * * @param int $id */ public function deleteLogements($id) { $this->delete('id = '.(int)$id); } } ?>
Mon controleur :
class LogementsController extends Zend_Controller_Action { public function init() { /* Initialize action controller here */ } public function indexAction() { $this->view->title = "Logements"; $this->view->headTitle($this->view->title); $logements = new Application_Model_DbTable_Logements(); $this->view->logements = $logements->fetchAll(); } public function addAction() { $this->view->title = "Ajouter une nouvelle Entreprise"; $this->view->headTitle($this->view->title); $form = new Application_Form_Logements(); $form->submit->setLabel('Ajouter'); $this->view->form = $form; if ($this->getRequest()->isPost()) { $formData = $this->getRequest()->getPost(); if ($form->isValid($formData)) { $nom = $form->getValues('nom'); $type = $form->getValues('type'); $surface = $form->getValues('surface'); $nbchambre = $form->getValues('nbchambre'); $nbpersonne = $form->getValues('nbpersonne'); $adresse = $form->getValues('adresse'); $ville = $form->getValues('ville'); $codepostal = $form->getValues('codepostal'); $telephonefix = $form->getValues('telephonefix'); $internet = $form->getValues('internet'); $piscine = $form->getValues('piscine'); $vue = $form->getValues('vue'); $distancemer = $form->getValues('distancemer'); $url = $form->getValues('url'); $Entreprises_id = $form->getValues('Entreprises_id'); $Personnes_id = $form->getValues('Personnes_id'); $actif = $form->getValue('actif'); $logements = new Application_Model_DbTable_Logements(); $logements->addLogements($nom,$type,$surface, $nbchambre, $nbpersonne, $adresse,$codepostal, $ville,$telephonefix,$internet, $piscine, $vue, $distancemer,$url, $Entreprises_id,$Personnes_id, $actif); $this->_helper->redirector('index'); } else { $form->populate($formData); } } } public function editAction() { $this->view->title = "Editer le Logement"; $this->view->headTitle($this->view->title); $form = new Application_Form_Logements(); $form->submit->setLabel('Enregistrer'); $this->view->form = $form; if ($this->getRequest()->isPost()) { $formData = $this->getRequest()->getPost(); if ($form->isValid($formData)) { $id = (int)$form->getValue('id'); $nom = $form->getValues('nom'); $type = $form->getValues('type'); $surface = $form->getValues('surface'); $nbchambre = $form->getValues('nbchambre'); $nbpersonne = $form->getValues('nbpersonne'); $adresse = $form->getValues('adresse'); $ville = $form->getValues('ville'); $codepostal = $form->getValues('codepostal'); $telephonefix = $form->getValues('telephonefix'); $internet = $form->getValues('internet'); $piscine = $form->getValues('piscine'); $vue = $form->getValues('vue'); $distancemer = $form->getValues('distancemer'); $url = $form->getValues('url'); $Entreprises_id = $form->getValues('Entreprises_id'); $Personnes_id = $form->getValues('Personnes_id'); $actif = $form->getValue('actif'); $logements = new Application_Model_DbTable_Logements(); $logements->updateLogements($id,$nom,$type,$surface, $nbchambre, $nbpersonne, $adresse,$codepostal, $ville,$telephonefix,$internet, $piscine, $vue, $distancemer,$url, $Entreprises_id,$Personnes_id, $actif); $this->_helper->redirector('index'); } else { $form->populate($formData); } } else { $id = $this->_getParam('id', 0); if ($id > 0) { $logements = new Application_Model_DbTable_Logements(); $form->populate($logements->getLogements($id)); } } } public function deleteAction() { $this->view->title = "Effacer un Logement"; $this->view->headTitle($this->view->title); if ($this->getRequest()->isPost()) { $del = $this->getRequest()->getPost('del'); if ($del == 'Yes') { $id = $this->getRequest()->getPost('id'); $logements = new Application_Model_DbTable_Logements(); $logements->deleteLogements($id); } $this->_helper->redirector('index'); } else { $id = $this->_getParam('id', 0); $logements = new Application_Model_DbTable_Logements(); $this->view->logements = $logements->getLogements($id); } } }
Petite question, le SQL n'est pas sensible à la casse. Pourquoi dans mon code, lorsque je donne le nom de ma table dans ma classe, je dois absolument faire attention à la casse ?
Est-ce propre à MySQL ? à Zend ? au PHP ?
Dernière modification par francoisA (23-07-2010 23:34:59)
Hors ligne
Salut,
ta table est formée avec plusieurs clefs primaires.
essaye de rajouter ça à ta classe Zend_Db_Table_Abstract
protected $_primary = array('id','Entreprises_id', 'Personnes_id');
+ les références aux autres tables/classes
protected $_referenceMap ....
@+
Dernière modification par _Fuse_ (09-07-2010 14:37:32)
Hors ligne
J'ai ajouté ceci à ma classe :
class Application_Model_DbTable_logements extends Zend_Db_Table_Abstract { protected $_name = 'Logements'; protected $_primary = array('id','Entreprises_id', 'Personnes_id'); protected $_referenceMap = array( 'Entreprise'=> array( 'Columns'=>array('Entreprises_id'), 'refTableClass'=>'Entreprises', 'refColumns'=>array('Entreprises_id') ), 'Personne'=> array( 'Columns'=>array('Personnes_id'), 'refTableClass'=>'Personnes', 'refColumns'=>array('Personnes_id') ) );
Mais j'ai toujours la meme erreur.
Hors ligne
J'aimerai vraiment que tu me montres ta méthode qui m'a l'air full Zend.
Néanmoins, j'ai réussit à faire passer le truc en castant mes id. Mais il m'ajoute "ARRAY" dans ma base au lieu du contenu des champs !!!!
Cela donne pour l'ajout :
public function addLogements($nom, $type, $surface, $nbchambre,$nbpersonne, $adresse, $codepostal,$ville, $telephonefix,$internet,$piscine,$vue,$distancemer, $url, $Entreprises_id, $Personnes_id, $actif) { $data = array( 'nom' => $nom, 'type' => $type, 'surface'=> $surface, 'nbchambre'=>$nbchambre, 'nbpersonne'=>$nbpersonne, 'adresse'=>$adresse, 'ville'=>$ville, 'codepostal'=>$codepostal, 'telephonefix'=>$telephonefix, 'internet'=> $internet, 'piscine'=> $piscine, 'vue'=>$vue, 'distancemer'=>$distancemer, 'url'=>$url, 'Entreprises_id'=>(int)$Entreprises_id, 'Personnes_id'=>(int)$Personnes_id, 'actif'=>$actif, ); $this->insert($data); }
Dernière modification par francoisA (09-07-2010 22:56:35)
Hors ligne
Donc Apres avoir lu les doc Zend sur le gestion des clefs étrangères, j'ai fait ceci :
class Application_Model_DbTable_logements extends Zend_Db_Table_Abstract { protected $_name = 'Logements'; protected $_primary = 'id'; protected $_referenceMap = array( 'Entreprise'=> array( 'columns'=>array('Entreprises_id'), 'refTableClass'=>'Application_Model_DbTable_Entreprises', 'refColumns'=>array('id') ), 'Personne'=> array( 'columns'=>array('Personnes_id'), 'refTableClass'=>'Personnes', 'refColumns'=>array('id') ) );
CREATE TABLE IF NOT EXISTS `martinique`.`Entreprises` ( `id` INT NOT NULL AUTO_INCREMENT , `nom` VARCHAR(50) NOT NULL , `responsable` VARCHAR(25) NULL , `siret` CHAR(14) NULL , `rib` CHAR(34) NULL , `telephonefix` VARCHAR(10) NULL , `telephoneport` VARCHAR(10) NULL , `telephonefax` VARCHAR(10) NULL , `adresse` VARCHAR(255) NULL , `codepostal` VARCHAR(7) NULL , `ville` VARCHAR(45) NULL , `email` VARCHAR(100) NULL , `url` VARCHAR(255) NULL , `horaire` VARCHAR(255) NULL , `actif` TINYINT(4) NULL , `pays` CHAR(2) NULL , PRIMARY KEY (`id`) ) ENGINE = InnoDB; CREATE TABLE IF NOT EXISTS `martinique`.`Logements` ( `id` INT NOT NULL AUTO_INCREMENT , `nom` VARCHAR(50) NOT NULL , `type` VARCHAR(45) NULL , `surface` FLOAT NULL , `nbchambre` INT NULL , `nbpersonne` INT NULL , `adresse` VARCHAR(255) NULL , `codepostal` VARCHAR(7) NULL , `ville` VARCHAR(45) NULL , `telephonefix` VARCHAR(10) NULL , `internet` CHAR(1) NULL , `piscine` CHAR(1) NULL , `vue` CHAR(1) NULL , `distancemer` INT NULL , `url` VARCHAR(255) NULL , `Entreprises_id` INT NOT NULL , `Personnes_id` INT NOT NULL , `actif` TINYINT(4) NULL , PRIMARY KEY (`id`, `Entreprises_id`, `Personnes_id`) , INDEX `fk_Logement_Entreprise1` (`Entreprises_id` ASC) , INDEX `fk_Logement_Personne1` (`Personnes_id` ASC) , CONSTRAINT `fk_Logement_Entreprise1` FOREIGN KEY (`Entreprises_id` ) REFERENCES `martinique`.`Entreprises` (`id` ) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `fk_Logement_Personne1` FOREIGN KEY (`Personnes_id` ) REFERENCES `martinique`.`Personnes` (`id` ) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB;
Est-ce correct ?
Dernière modification par francoisA (12-07-2010 04:26:37)
Hors ligne
Bonjour,
Je vois que tu as corriger certaines parties du code.
ça ma l'air correct à part le :
'Personne'=> array(
'columns'=>array('Personnes_id'),
'refTableClass'=>'Personnes',
'refColumns'=>array('id')
)
à modifier comme ceci (je suppose, je ne connais pas tes classes) :
'Personne'=> array(
'columns'=>array('Personnes_id'),
'refTableClass'=>'Application_Model_DbTable_Personnes',
'refColumns'=>array('id')
)
Dernière modification par _Fuse_ (12-07-2010 08:52:38)
Hors ligne
Petit plus,
Je prends un exemple de suppression en cascade
schéma des tables :
[task]<----->[task_has_target]<------>[target]
la table task_has_target a pour clef primaire la clef primaire de task+target.
Dans la Classe "Application_Model_DbTable_TaskHasTarget" voici le code qui sert à faire le lien vers la table 'task"
nb : le paramètre important 'onDelete'
protected $_referenceMap = array(
'Task' => array(
'columns' => array('task_idtask'),
'refTableClass' => 'Application_Model_DbTable_Task',
'refColumns' => 'idtask',
'onDelete' => self::CASCADE
et enfin pour supprimer automatiquement les enregistrements d'une clef qui ce trouve à la fois dans la table task et la table task_has_target
$table = new Application_Model_DbTable_Task();
$taskRow = $table->find($id);
$taskDelete = $taskRow->current();
$taskDelete->delete();
Je donne ce bout de code car il me semble que j'avais le même type d'erreur avant d'ajouter la suppression en cascade.
Dernière modification par _Fuse_ (12-07-2010 10:41:05)
Hors ligne
En effet, pour le nom de ma classe j'ai oublié de la mettre à jour dans le refTable.
Pour le Delete cascade, j'hésite à le mettre en place. J'ai tendance à penser que la perte de données et toujours préjudiciable... Si je le fais, je pense le faire via le SQL. Il me semble avoir lu dans la doc Zend qu'il fallait mieux laisser gérer la BDD.
Mais je garde ton code sous le coude, çà peut toujours servir.
Merci pour ton aide
Hors ligne