Shopping : les données 2/2

Dans le précédent article on a mis en place les migrations, les modèles et les factories pour gérer les données de la boutique. Nous allons à présent établir les relations entre les tables avec Eloquent. Il nous faudra aussi créer quelques accessors et mutators pour simplifier le code. On terminera en définissant la population pour avoir nos données d’exemple.

Vous pouvez télécharger un ZIP du projet ici.

Les relations

User

Un utilisateur a des adresses et des commandes :

public function addresses()
{
    return $this->hasMany(Address::class);
}

public function orders()
{
    return $this->hasMany(Order::class);
}

Order

Une commande a plusieurs adresses et plusieurs produits, d’autre part elle appartient à un utilisateur et un état. Enfin elle a une information de paiement :

public function adresses()
{
    return $this->hasMany(OrderAddress::class);
}

public function products()
{
    return $this->hasMany(OrderProduct::class);
}

public function state()
{
    return $this->belongsTo(State::class);
}

public function user()
{
    return $this->belongsTo(User::class);
}

public function payment_infos()
{
    return $this->hasOne(Payment::class);
}

On met aussi en place quelques accesseurs :

public function getPaymentTextAttribute($value)
{
    $texts = [
      'carte' => 'Carte bancaire',
      'virement' => 'Virement',
      'cheque' => 'Chèque',
      'mandat' => 'Mandat administratif',
    ];

    return $texts[$this->payment];
}

public function getTotalOrderAttribute()
{
    return $this->total + $this->shipping;
}

public function getTvaAttribute()
{
    return $this->tax > 0 ? $this->total / (1 + $this->tax) * $this->tax : 0;
}

public function getHtAttribute()
{
    return $this->total / (1 + $this->tax);
}

On verra leur utilité plus tard.

Address

Une adresse appartient à un pays :

public function country()
{
    return $this->belongsTo(Country::class);
}

Country

Un pays a plusieurs plages de poids (et réciproquement), adresses et adresses de commande :

public function ranges()
{
    return $this->belongsToMany(Range::class, 'colissimos')->withPivot('id', 'price');
}

public function addresses()
{
    return $this->hasMany(Address::class);
}

public function order_addresses()
{
    return $this->hasMany(OrderAddress::class);
}

On récupère les frais de port dans la table pivot colissimos.

OrderAddress

Une adresse de commande appartient à un pays :

public function country()
{
    return $this->belongsTo(Country::class);
}

On part du principe que les pays sont stables dans la base.

Range

Les plages de poids sont en relation n:n avec les pays avec colissimos comme pivot :

public function countries()
{
    return $this->belongsToMany(Country::class, 'colissimos')->withPivot('price');
}

State

Un état a plusieurs commandes :

public function orders()
{
    return $this->hasMany(Order::class);
}

Synthèse

Voici un schéma global pour visualiser ces relations :

La population

Il ne nous reste plus qu’à coder la population dans la classe DatabaseSeeder :

<?php

use Illuminate\Database\Seeder;
use App\Models\ { User, Address, Country, Product, Colissimo, Range, State, Shop, Page, Order };
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {        
        Country::insert([
            ['name' => 'France', 'tax' => 0.2],
            ['name' => 'Belgique', 'tax' => 0.2],
            ['name' => 'Suisse', 'tax' => 0],
            ['name' => 'Canada', 'tax' => 0],
        ]);

        Range::insert([
            ['max' => 1],
            ['max' => 2],
            ['max' => 3],
            ['max' => 100],
        ]);

        Colissimo::insert([
            ['country_id' => 1, 'range_id' => 1, 'price' => 7.25],
            ['country_id' => 1, 'range_id' => 2, 'price' => 8.95],
            ['country_id' => 1, 'range_id' => 3, 'price' => 13.75],
            ['country_id' => 1, 'range_id' => 4, 'price' => 0],
            ['country_id' => 2, 'range_id' => 1, 'price' => 15.5],
            ['country_id' => 2, 'range_id' => 2, 'price' => 17.55],
            ['country_id' => 2, 'range_id' => 3, 'price' => 22.45],
            ['country_id' => 2, 'range_id' => 4, 'price' => 0],
            ['country_id' => 3, 'range_id' => 1, 'price' => 15.5],
            ['country_id' => 3, 'range_id' => 2, 'price' => 17.55],
            ['country_id' => 3, 'range_id' => 3, 'price' => 22.45],
            ['country_id' => 3, 'range_id' => 4, 'price' => 0],
            ['country_id' => 4, 'range_id' => 1, 'price' => 27.65],
            ['country_id' => 4, 'range_id' => 2, 'price' => 38],
            ['country_id' => 4, 'range_id' => 3, 'price' => 55.65],
            ['country_id' => 4, 'range_id' => 4, 'price' => 0],
        ]);

        State::insert([
            ['name' => 'Attente chèque', 'slug' => 'cheque', 'color' => 'blue', 'indice' => 1],
            ['name' => 'Attente mandat administratif', 'slug' => 'mandat', 'color' => 'blue', 'indice' => 1],
            ['name' => 'Attente virement', 'slug' => 'virement', 'color' => 'blue', 'indice' => 1],
            ['name' => 'Attente paiement par carte', 'slug' => 'carte', 'color' => 'blue', 'indice' => 1],
            ['name' => 'Erreur de paiement', 'slug' => 'erreur', 'color' => 'red', 'indice' => 0],
            ['name' => 'Annulé', 'slug' => 'annule', 'color' => 'red', 'indice' => 2],
            ['name' => 'Mandat administratif reçu', 'slug' => 'mandat_ok', 'color' => 'green', 'indice' => 3],
            ['name' => 'Paiement accepté', 'slug' => 'paiement_ok', 'color' => 'green', 'indice' => 4],
            ['name' => 'Expédié', 'slug' => 'expedie', 'color' => 'green', 'indice' => 5],
            ['name' => 'Remboursé', 'slug' => 'rembourse', 'color' => 'red', 'indice' => 6],
        ]);

        factory(User::class, 20)
          ->create()
          ->each(function ($user) {
              $user->addresses()->createMany(
                  factory(Address::class, mt_rand(2, 3))->make()->toArray()
              );
        });

        $user = User::find(1);
        $user->admin = true;
        $user->save();

        factory(Product::class, 6)->create();

        factory(Shop::class)->create();

        $items = [
            ['livraisons', 'Livraisons'],
            ['mentions-legales', 'Mentions légales'],
            ['conditions-generales-de-vente', 'Conditons générales de vente'],
            ['politique-de-confidentialite', 'Politique de confidentialité'],
            ['respect-environnement', 'Respect de l\'environnement'],
            ['mandat-administratif', 'Mandat administratif'],
        ];

        foreach($items as $item) {
            factory(Page::class)->create([
                'slug' => $item[0],
                'title' => $item[1],
            ]);
        }

        factory(Order::class, 30)
          ->create()
          ->each(function ($order) {
              $address = $order->user->addresses()->take(1)->get()->makeHidden(['id', 'user_id'])->toArray();
              $order->adresses()->create($address[0]);
              if(mt_rand(0, 1)) {
                  $address = $order->user->addresses()->skip(1)->take(1)->get()->makeHidden(['id', 'user_id'])->toArray();
                  $address[0]['facturation'] = false;
                  $order->adresses()->create($address[0]);                 
              } 
              $countryId = $address[0]['country_id'];
              $total = 0;
              $product = Product::find(mt_rand(1, 3));
              $quantity = mt_rand(1, 3);
              $price = $product->price * $quantity;
              $total = $price; 
              $order->products()->create(
                  [
                      'name' => $product->name,
                      'total_price_gross' => $price,
                      'quantity' => $quantity,
                  ]
              );
              if(mt_rand(0, 1)) {
                  $product = Product::find(mt_rand(4, 6));
                  $quantity = mt_rand(1, 3);
                  $price = $product->price * $quantity;
                  $total += $price;
                  $order->products()->create(
                      [
                          'name' => $product->name,
                          'total_price_gross' => $price,
                          'quantity' => $quantity,
                      ]
                  ); 
              }
              if($order->payment === 'carte' && $order->state_id === 8) {
                  $order->payment_infos()->create(['payment_id' => (string) Str::uuid()]);
              }
              $order->tax = $countryId > 2 ? 0 : .2;
              $order->total = $total;
              $order->save();
        });
    }
}

On préserve un minimum de cohérence sans trop alourdir le code.

On crée ainsi :

  • 4 pays
  • 4 plages de poids
  • des frais de ports par pays et par plage
  • 10 états de commande
  • 20 utilisateurs
  • 6 produits
  • une boutique
  • 6 pages d’information
  • 30 commandes

La migration

On arrive à l’instant de vérité en lançant les migrations et la population :

php artisan migrate --seed
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.44 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.47 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.3 seconds)
Migrating: 2020_05_07_213250_create_countries_table
Migrated:  2020_05_07_213250_create_countries_table (0.22 seconds)
Migrating: 2020_05_07_213940_create_addresses_table
Migrated:  2020_05_07_213940_create_addresses_table (1.61 seconds)
Migrating: 2020_05_08_111839_create_products_table
Migrated:  2020_05_08_111839_create_products_table (0.21 seconds)
Migrating: 2020_05_08_113300_create_states_table
Migrated:  2020_05_08_113300_create_states_table (0.26 seconds)
Migrating: 2020_05_08_113820_create_orders_table
Migrated:  2020_05_08_113820_create_orders_table (1.53 seconds)
Migrating: 2020_05_08_115032_create_order_addresses_table
Migrated:  2020_05_08_115032_create_order_addresses_table (2 seconds)
Migrating: 2020_05_08_115544_create_order_products_table
Migrated:  2020_05_08_115544_create_order_products_table (1.15 seconds)
Migrating: 2020_05_08_120042_create_ranges_table
Migrated:  2020_05_08_120042_create_ranges_table (0.24 seconds)
Migrating: 2020_05_08_120616_create_colissimos_table
Migrated:  2020_05_08_120616_create_colissimos_table (1.69 seconds)
Migrating: 2020_05_08_120946_create_shops_table
Migrated:  2020_05_08_120946_create_shops_table (0.24 seconds)
Migrating: 2020_05_08_122452_create_pages_table
Migrated:  2020_05_08_122452_create_pages_table (0.44 seconds)
Migrating: 2020_05_08_122809_create_payments_table
Migrated:  2020_05_08_122809_create_payments_table (1.05 seconds)
Migrating: 2020_05_08_123334_create_notifications_table
Migrated:  2020_05_08_123334_create_notifications_table (1.03 seconds)
Database seeding completed successfully.

 

Si vous n’otenez pas ce résultat c’est que vous avez oublié quelque chose en cours de route !

Si on regarde les utilisateurs créés on voit que le premier est administrateur :

Les images

Chaque produit a une image pour le visualiser. Pour stocker ces images on crée un dossier public/images avec un sous-dossier thumbs pour disposer d’une image en plus faible définition pour la page d’accueil des produits :

Ces images sont dans le fichier téléchargeable du projet.

Conclusion

On a maintenant notre schéma en place et des données pour pouvoir s’amuser. Dans le prochain article on commencera à coder la boutique !

Print Friendly, PDF & Email

30 commentaires sur “Shopping : les données 2/2

  1. Je n’arrive pas à faire la migration
    Voici l’erreur

    C:\xampp\htdocs\boutique> php artisan migrate:fresh
    Dropped all tables successfully.
    Migration table created successfully.
    Migrating: 2014_10_12_000000_create_users_table
    Migrated: 2014_10_12_000000_create_users_table (1.42 seconds)
    Migrating: 2014_10_12_100000_create_password_resets_table
    Migrated: 2014_10_12_100000_create_password_resets_table (0.76 seconds)
    Migrating: 2019_08_19_000000_create_failed_jobs_table
    Migrated: 2019_08_19_000000_create_failed_jobs_table (0.38 seconds)
    Migrating: 2020_06_07_153441_create_users

    Illuminate\Database\QueryException

    SQLSTATE[42S01]: Base table or view already exists: 1050 Table ‘users’ already exists (SQL: create table `users` (`id` bigint unsigned not null auto_increment primary key, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate ‘utf8mb4_unicode_ci’)

    at C:\xampp\htdocs\boutique\vendor\laravel\framework\src\Illuminate\Database\Connection.php:671
    667| // If an exception occurs when attempting to run a query, we’ll format the error
    668| // message to include the bindings with SQL, which will make this exception a
    669| // lot more helpful to the developer instead of just the database’s errors.
    670| catch (Exception $e) {
    > 671| throw new QueryException(
    672| $query, $this->prepareBindings($bindings), $e
    673| );
    674| }
    675|

    1 C:\xampp\htdocs\boutique\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOException::(« SQLSTATE[42S01]: Base table or view already exists: 1050 Table ‘users’ already exists »)

    2 C:\xampp\htdocs\boutique\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOStatement::execute()

  2. Bonjour à toutes et à tous
    j’ai une erreur dans mes code vous pouvez m’aider?

    Unable to locate factory for [App\Models\Address].

    at C:\laragon\www\assigbe\vendor\laravel\framework\src\Illuminate\Database\Eloquent\FactoryBuilder.php:273
    269| */
    270| protected function getRawAttributes(array $attributes = [])
    271| {
    272| if (! isset($this->definitions[$this->class])) {
    > 273| throw new InvalidArgumentException(« Unable to locate factory for [{$this->class}]. »);
    274| }
    275|
    276| $definition = call_user_func(
    277| $this->definitions[$this->class],

  3. Bonjour Bestmomo. Un très grand merci pour ta générosité et ta contribution à l’agrandissement de la communauté autour de Laravel. Je te suis maintenant depuis la version 5.5 et ce que tu fais, c’est vraiment du bon boulot.
    J’ai récemment entamé la version 7 de Laravel et comme d’hab j’ai choisi de le suivre avec tes tutos.
    J’ai jusqu’ici très bien suivi les instructions dans la première partie et je me retrouve bloqué dans la suite.

    Lorsque j’exécute php artisan migrate , j’obtiens l’erreur suivante :

    Illuminate\Database\QueryException

    could not find driver (SQL: select * from information_schema.tables where table_schema = shopping and table_name = migrations and table_type = ‘BASE TABLE’)

    at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667| // If an exception occurs when attempting to run a query, we’ll format the error
    668| // message to include the bindings with SQL, which will make this exception a
    669| // lot more helpful to the developer instead of just the database’s errors.
    670| catch (Exception $e) {
    > 671| throw new QueryException(
    672| $query, $this->prepareBindings($bindings), $e
    673| );
    674| }
    675|

    +34 vendor frames
    35 artisan:37
    Illuminate\Foundation\Console\Kernel::handle()

    Le résultat est le même pour php artisan migrate –seed ou encore php artisan migrate:install

    J’ai fais de nombreuses recherches sur des forums puis essayer les solutions qui revenaient le plus souvent à savoir : décommenter la ligne

    ;extension=php_pdo_mysql.dll ====> chose faite ; mais l’erreur persiste toujours.

    Mon fichier .env présente les configurations suivantes :

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=shopping
    DB_USERNAME=root
    DB_PASSWORD=

    Configurations système :
    OS : Ubuntu Linux LTS 20
    J’utilise xampp pour linux dans sa version la plus récente.

    Merci de m’aider.

  4. Bonjour BestMomo.
    Quand je fais php artisan migrate –seed j’obtiens l’erreur suivante : mt_rand() expects parameter 2 to be integer, float given. J’ai effectué quelques recherches sur google mais j’ai pas toujours de solution. Besoin de votre aide s’il vous plait. Merci

      1. Salut, je reviens encore à la charge désolé mais j’ai envie d’avancer. Je me demande si ce n’est pas le fait d’avoir fait des divisions dans le ProductFactory qui pose ce problème?
        $factory->define(Product::class, function (Faker $faker) {
        return [
        ‘name’ => $faker->sentence(3),
        ‘price’ => mt_rand(100, 1000) / 10.0,
        ‘weight’ => mt_rand(1, 4) / 1.8,
        ‘quantity’ => 50,
        ‘active’ => $faker->boolean(),
        ‘image’ => strval(mt_rand(1, 5)) . ‘.jpg’,
        ‘description’ => $faker->paragraph(),
        ];
        });

        Merci d’avance pour votre réponse.

        1. Bonjour BestMomo, Voici l’erreur qui s’affiche dans la console:

          mt_rand() expects parameter 2 to be integer, float given

          at C:\xampp\htdocs\shopping\vendor\fzaninotto\faker\src\Faker\Provider\Base.php:140
          136| public static function numberBetween($int1 = 0, $int2 = 2147483647)
          137| {
          138| $min = $int1 < $int2 ? $int1 : $int2;
          139| $max = $int1 140| return mt_rand($min, $max);
          141| }
          142|
          143| /**
          144| * Returns the passed value

          1 C:\xampp\htdocs\shopping\vendor\fzaninotto\faker\src\Faker\Provider\Base.php:140
          mt_rand()

          2 C:\xampp\htdocs\shopping\vendor\fzaninotto\faker\src\Faker\Generator.php:228
          Faker\Provider\Base::numberBetween().

          1. Rebonjour, après avoir fait un php artisan migrate:fresh tout est normal maintenant. merci pour votre disponibilité

  5. bonjour je viens de découvrir ton tuto, merci cela va beaucoup m’aider.
    J’ai juste un soucis a la migration, cela concerne la table des notifications
    SQLSTATE[42000]: Syntax error or access violation: 1071 La clé est trop longue. Longueur maximale: 1000 (SQL: alter table ‘notifications’ add index ‘notifications_notifiable_type_notifiable_id_index’ (‘notifiable_type’, ‘notifiable_id’))
    De memoire, c’est un pb de longueur de champ, mais lequel dois-je changer et en quel type ?
    merci

    1. Salut,

      C’est ta version de MySQL qui est un peu vieille. Soit tu l’actualises, soit tu ajoutes à app/Providers/AppServiceProvider.php :

      use Illuminate\Support\Facades\Schema;

      public function boot()
      {
      Schema::defaultStringLength(191);
      }

  6. Bonjour.

    Je découvre ce blog et franchement bravo !

    J’ai téléchargé le fichier zip du projet, mais les fichiers ne semblent pas exploitables (à moins que je me trompe).

    Il s’agit d’une liste non ordonnée et non classée par dossiers…

    C’est normal peut-être ?

    Encore bravo !

      1. Merci pour ta réponse.

        Je suis sous linux.

        J’ai cette erreur au lancement du seed et je voulais comparer mon code au tien :

        SQLSTATE[HY000]: General error: 1005 Can’t create table `shopping`.`colissimos` (errno: 150 « Foreign key constraint is incorrectly formed ») (SQL: alter table `colissimos` add constraint `colissimos_range_id_foreign` foreign key (`range_id`) references `ranges` (`id`) on delete cascade)

        Merci !

  7. Salut
    Bestmomo your are the best…. MERCI POUR TOUT TES TUTOS
    Voici l’erreur que j’obtiens quand je fais un migrate –seed … cela est due au type enum du champ payment … pour pourvoir continuer le projet j’ai due remplacer le « enum » par « string » et supprimer le tableau dans la migration…
    -y a t-il une autre façon de remédier a ce problème ?
    -aurai je des souci plus tard suite au fait que j’ai utilise « string » au lieu de « enum » ?

    Illuminate\Database\QueryException

    SQLSTATE[01000]: Warning: 1265 Data truncated for column ‘payment’ at row 1 (SQL: insert into `orders` (`reference`, `shipping`, `payment`, `state_id`, `user_id`, `purchase_order`, `pick`, `total`, `tax`, `invoice_id`, `invoice_number`, `created_at`, `updated_at`) values (UR8POADX, 13.77, virment, 8, 15, ?, 0, 0, 0, ?, ?, 2019-02-12 00:22:49, 2020-05-12 08:39:12))

    at E:\projets\www\bestshopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:671
    667| // If an exception occurs when attempting to run a query, we’ll format the error
    668| // message to include the bindings with SQL, which will make this exception a
    669| // lot more helpful to the developer instead of just the database’s errors.
    670| catch (Exception $e) {
    > 671| throw new QueryException(
    672| $query, $this->prepareBindings($bindings), $e
    673| );
    674| }
    675|

    1 E:\projets\www\bestshopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOException::(« SQLSTATE[01000]: Warning: 1265 Data truncated for column ‘payment’ at row 1 »)

    2 E:\projets\www\bestshopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOStatement::execute()

  8. Bestmomo j’ai cette exception lorsque j’exécute la migration
    Illuminate\Database\QueryException

    SQLSTATE[HY000]: General error: 1005 Can’t create table `shopping`.`models_addresses` (errno: 150 « Foreign key constraint is incorrectly formed ») (SQL: alter table `models_addresses` add constraint `models_addresses_country_id_foreign` foreign key (`country_id`) references `countries` (`id`) on delete cascade)

    at D:\projetlaravel\shopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:671
    667| // If an exception occurs when attempting to run a query, we’ll format the error
    668| // message to include the bindings with SQL, which will make this exception a
    669| // lot more helpful to the developer instead of just the database’s errors.
    670| catch (Exception $e) {
    > 671| throw new QueryException(
    672| $query, $this->prepareBindings($bindings), $e
    673| );
    674| }
    675|

    1 D:\projetlaravel\shopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOException::(« SQLSTATE[HY000]: General error: 1005 Can’t create table `shopping`.`models_addresses` (errno: 150 « Foreign key constraint is incorrectly formed ») »)

    2 D:\projetlaravel\shopping\vendor\laravel\framework\src\Illuminate\Database\Connection.php:464
    PDOStatement::execute()

Laisser un commentaire