Laravel 5

Import de masse depuis un CSV exporté d'une BDD tierce.

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    37

    Bonjour les amis,

    Alors voilà ma situation:

    Je suis en train de monter une platefome (Laravel) qui va se connecter à un WebService (Lumen). Jusqu'ici tout va bien.
    Cette plateforme à pour but de cataloguer des offres commerciales avec les cadeaux associés (genre 1 abonnement de 6 mois acheté, un grille pain offert). Ici aussi, tout est au mieux. Mais attendez, le lièvre arrive.

    Pour alimenter le WebService qui va fournir les données à ma plateforme, un CSV sera exporté d'une base de données et importé dans celle de mon WebService (je n'ai pas accès à la base d'où provient le CSV).

    Le problème est que la base de donnée d'où provient le CSV n'a pas la même structure que le base de données du WebService. Les noms des champs sont differents et ils sont organisés différemment dans les tables.

    Cet import sera fait une fois par mois (je tâcherai de mettre un Task Schedule pour automatiser ça), mais ce n'est pas ici le sujet. Et cette procédure sera également faite avec d'autres WebServices (et le CSV viendra donc encore d'une autre base de données) car les users auront accès à tel ou tel WebService.

    Ma première étape est donc d'arriver à importer les données d'un seul CSV vers un seul WebService. La répétition et la déclinaison vers d'autres WebServices, je l'espère, viendront facilement après.

    Avez-vous une procédure à mon conseiller pour importer les données d'un fichier CSV vers une base de données qui n'a pas la même structure que la base d'où provient le CSV (donc avec des noms de champs differents) ?

    J'espère avoir été assez clair...

    Merci en tout cas.

    La plateforme est en Laravel 5.7.13 et le Webservice en Lumen 5.7.5.

  • Avatar de nash
    Membre depuis :
    16/04/2019
    Messages :
    14

    Salut,

    detail d'un exemple concret :
    Pour un client nous recevons un fichier cvs poliris (exploitation de données immobilières), ce fichier csv comporte plus de trois cent champs.

    Etape 1 : Ouvert et lu comme un vulgaire fichier texte suite à un formatage de colonne completement batard.
    Etape 2 : Chaque ligne est splité avec un regex et injecté dans un tableau de collection.
    Etape 3 : Le tableau de collection est injecté dans une collection principal (pourqoi les collections uniquement pour l'iteration object).
    Etape 4 : La moulinette, on y va à grand coup de updateOrCreate selon le model, les syncs de relations (pour suivre les differences entre chaque import on tags uniquement un status active ou deleted)
    Etape 5 : L'automatisation, On a crée une commande artisan que l'on appel via un job depuis l'ihm pour les imports manuel. La commande artisan classique en console.
    Etape 6 : la delaration du process job sur le system qui l'heberge plus le cron.

    C'est l'essentiel, a suivre...

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    37

    Merci nash pour ta réponse, je vais regarder ça. Une collection c'est vrai que je n'y ai pas pensé, c'est une idée pas mal.

    Pour l'instant, ma reflexion m'avait porté sur ça (j'aime bien découper les étapes) :

    <?php

    $csv = array_map('str_getcsv', file('file.csv'));//transforme le csv en array
    $titles = array_shift($csv);//crée un array avec le contenu de $csv[0] en le supprimant de $csv

    $ArrayFromTableCsv = [];
    $l = 0; //compteur pour le nombre de lignes

    //on rassemble les données vers un array de array ordonnés
    foreach($csv as $row){
    $c = 0;//compteur pour le nombre de champs (pour recup le nom du champs avec $titles[$m])
    foreach($row as $champ){
    $ArrayFromTableCsv[$l][$titles[$c]] = $champ;//$data[0]['nom'] = 'michel'
    $c++;
    }
    $l++;
    }

    $toTableUsers = [];
    $toTableAdresses = [];
    $toTableContacts = [];

    $r = 0; //compteur pour le dispatching suivant;

    //on dispatche les données vers des tableaux avec la logique 1 array = une table
    foreach($ArrayFromTableCsv as $row){
    if(isset($row['nom'])) $toTableUsers[$r]['name'] = $row['nom'];
    if(isset($row['prenom'])) $toTableUsers[$r]['first_name'] = $row['prenom'];
    if(isset($row['telephone'])) $toTableContacts[$r]['phone'] = $row['telephone'];
    if(isset($row['email'])) $toTableContacts[$r]['email'] = $row['email'];
    if(isset($row['adresse'])) $toTableAdresses[$r]['adress'] = $row['adresse'];
    if(isset($row['ville'])) $toTableAdresses[$r]['city'] = $row['ville'];
    if(isset($row['codepostal'])) $toTableAdresses[$r]['codep'] = $row['codepostal'];
    $r++;
    }

    //what if differents csv from differentes tables ? => implémenter les array $toTableUsers et compagnie également...

    //on va créer les objets
    foreach($toTableUsers as $row){
    $users = new Users([
    'name' => $row['name'],
    'firstname' => $row['firstname']
    ]);
    $users->save;
    }

    foreach($toTableAdresses as $row){
    $adresses = new Adresses([
    'adresse' => $row['adresse'],
    'ville' => $row['ville'],
    'codepostal' => $row['codepostal']
    ]);
    $adresses->save;
    }

    foreach($toTableContacts as $row){
    $contacts = new Contacts([
    'telephone' => $row['telephone'],
    'email' => $row['email']
    ]);
    $contacts->save;
    }

    Malheureusement, je n'ai pas encore le CSV donc je ne sais pas encore comment il sera organisé... Je ne sais pas encore exactement comment sera organisé ma BDD. Mais je vais creuser ton idée de collection (même si je ne suis pas encore familier des collection)

    A suivre...

    Ps: les autres idées sont également les bienvenues.

  • Avatar de nash
    Membre depuis :
    16/04/2019
    Messages :
    14

    Un peu de code pour etre dans le context

    le parsing

    namespace App\Libraries\Hektor;

    use Illuminate\Support\Collection;
    use Illuminate\Support\Facades\Storage;

    class Services
    {
    protected $fileName;
    protected $collection;
    public function __construct()
    {

    $this->fileName = Storage::disk('hektor')->getDriver()->getAdapter()->getPathPrefix();

    if (($handle = fopen ( $this->fileName.'Annonces.csv', 'r' )) !== FALSE) {

    $advert = [];

    while (!feof($handle)){
    $advert[] = new Collection(preg_split('/(^"|("!#)"?)/', utf8_encode(stream_get_line($handle, 1000000, "\n"))));
    }

    fclose ( $handle );

    array_pop($advert);
    $this->collection = new Collection($advert);
    }
    }

    public function all()
    {
    return $this->collection;
    }

    }

    La lecture est instancié via l'usine adverts.
    c'est juste une partie du concept mais de quoi t'orienter avec la manipulation du model

    public function Properties()
    {
    $this->properties = ( new Import())->adverts()->get();

    $this->properties->each(function ($property){
    //category_id
    $category = PropertyCategory::where('title', $property[3])->select('id')->first();
    //type_id
    $type = PropertyType::where('title', $property[4])->select('id')->first();
    //address_id
    $address = Address::updateOrCreate(
    ['city' => $property[6]],
    [
    'city' => $property[6],
    'zipcode' => $property[5],
    'country_id' => 74
    ]);
    $address->save();

    //SI TYPE TERRAIN
    //$area = $type->id === 16 ? $property[17] : $property[16] ;

    $product = Product::updateOrCreate(
    ['reference' => $property[2]],
    [
    'category_id' => $category->id,
    'type_id' => $type->id,
    'address_id' => $address->id,
    'price' => $property[11],
    'rooms' => $property[18],
    'bedrooms' => $property[19],
    'bathrooms' => (int)$property[29]+$property[30],
    'area' => $type->id === 16 ? $property[17] : $property[16], //TERRAIN OU AUTRE
    'terraces' => !empty($property[244]) ? $property[244] : 0,
    'parkings' => !empty($property[43]) ? $property[43] : 0,
    'furnished' => $property[26] === 'OUI' ? 1 : 0,
    'exclusivity' => $property[83] === 'OUI' ? 1 : 0,
    'dpe' => json_encode(['dpe' => ['letter' => $property[177], 'value' => $property[176]],'ges' => ['letter' => $property[179], 'value' => $property[178]]]),
    'district' => $property[9],
    ]
    );
    $product->save();

    /** PRODUCT TRANSLATE */
    ProductTranslate::UpdateOrCreate( //FR
    ['product_id' => $product->id, 'culture_id' => 1],
    [
    'title' => $property[20],
    'description' => $property[21],
    'slug' => str_slug($property[20],'-', 'fr'),
    ])->save();

    ProductTranslate::UpdateOrCreate( //EN
    ['product_id' => $product->id, 'culture_id' => 2],
    [
    'title' => $property[126],
    'description' => $property[127],
    'slug' => str_slug($property[126],'-', 'en'),
    ])->save();
    /** ***
    /

    /** PRODUCT EXPOSITION ****/
    $expositions = [];
    foreach ([35 => 'Sud',36 => 'Est',37 => 'Ouest',38 => 'Nord'] as $key => $value) {
    if(!empty($property[$key]) && $property[$key] === 'OUI') {
    $exposition = PropertyExposition::where('title', $value)->select('id')->first();
    $expositions[] = $exposition->id;
    }
    }
    $product->exposition()->sync($expositions);
    /** */

    /**** PRODUCT IMAGES **/
    $images = [];
    // Les urls qui n'existent plus dans le flux reste deleted
    $product->images()->update(['status' => 'deleted']);

    for ($i=85; $i<94; $i++) {
    if(!empty($property[$i])) {

    $filename = basename($property[$i],TRUE);
    $status = Storage::disk('public')->exists('properties/'.$filename)?'active':'new';

    $image = Image::UpdateOrCreate(
    ['url' => $property[$i]],
    [
    //'rank' => 1,
    'url' => $property[$i],
    'filename' => $filename,
    'status' => $status,
    'width_max' => $picture->width_max??0,
    'height_max' => $picture->height_max??0
    ]
    );

    $image->save();
    $images[] = $image->id;
    }
    }

    $product->images()->sync($images);
    /** *
    /
    });

    //passe les properties et leurs images qui ne sont plus dans le flux a deleted
    Product::whereNotIn('reference', array_column($this->properties->toArray(),2))->update(['status' => 'deleted']);

    }

    Dans ce cas la collection n'est vraiment utile à cause des index numeric.

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    37

    Finalement, la solution que j'ai choisir est la suivante:

    private function opeedr_lite($fileName)
    {
    $handle = fopen($fileName, "r");
    if($handle){
    while (($buffer = fgets($handle)) !== false) {
    $explode = explode("|", utf8_encode($buffer));
    $operation = new Coperations;
    $operation->OP_ID = $explode[26];
    if(!empty($explode[0])) $operation->OP_LIBE_OPL = $explode[0];
    if(!empty($explode[1])) $operation->OP_LIBE_OPAB = $explode[1];
    if(!empty($explode[2])) $operation->OP_CODE_OPAL = $explode[2];
    $operation->save();
    }
    }
    fclose($handle);
    }

    C'est très simple et ca ne crée pas de array contenant tout mon csv.
    Chaque ligne est traitée une par une

  • Avatar de VincentPIEPLU
    Membre depuis :
    04/04/2015
    Messages :
    235

    Bonjour Nash,

    Tu as fait une table par critère ?

Vous ne pouvez pas répondre à ce sujet.