![](https://laravel.sillo.org/storage/photos/2025/01/VL6SgpUeAioxYexSv39it25I7DgApxG8jWQ64TOT.png)
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 créant la population pour avoir nos données d'exemple.
Vous pouvez trouver le code dans ce dépôt Github.
Les relations
User
Un utilisateur a des adresses et des commandes :
use Illuminate\Database\Eloquent\Relations\HasMany;
...
public function addresses(): HasMany
{
return $this->hasMany(Address::class);
}
public function orders(): HasMany
{
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 :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Order extends Model
{
use HasFactory;
protected $fillable = [
'shipping', 'tax', 'user_id', 'state_id', 'payment', 'reference', 'pick', 'total',
];
public function addresses(): HasMany
{
return $this->hasMany(OrderAddress::class);
}
public function products(): HasMany
{
return $this->hasMany(OrderProduct::class);
}
public function state(): BelongsTo
{
return $this->belongsTo(State::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function payment_infos(): HasOne
{
return $this->hasOne(Payment::class);
}
}
On met aussi en place quelques accesseurs :
public function getPaymentTextAttribute($value): string
{
$texts = [
'carte' => 'Carte bancaire',
'virement' => 'Virement',
'cheque' => 'Chèque',
'mandat' => 'Mandat administratif',
];
return $texts[$this->payment];
}
public function getTotalOrderAttribute(): float
{
return $this->total + $this->shipping;
}
public function getTvaAttribute(): float
{
return $this->tax > 0 ? $this->total / (1 + $this->tax) * $this->tax : 0;
}
public function getHtAttribute(): float
{
return $this->total / (1 + $this->tax);
}
- getPaymentTextAttribute : cette fonction retourne une description textuelle du mode de paiement
- getTotalOrderAttribute : cette fonction calcule le montant total de la commande
- getTvaAttribute : cette fonction calcule le montant de la TVA
- getHtAttribute : cette fonction calcule le montant hors taxes (HT)
On verra leur utilité plus tard, ça nous simplifiera le codage.
Address
Une adresse appartient à un pays :
use Illuminate\Database\Eloquent\Relations\BelongsTo;
...
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
Country
Un pays a plusieurs plages de poids (et réciproquement), adresses et adresses de commande :
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
...
public function ranges(): BelongsToMany
{
return $this->belongsToMany(Range::class, 'colissimos')->withPivot('id', 'price');
}
public function addresses(): HasMany
{
return $this->hasMany(Address::class);
}
public function order_addresses(): HasMany
{
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 :
use Illuminate\Database\Eloquent\Relations\BelongsTo;
...
public function country(): BelongsTo
{
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 :
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
...
public function countries(): BelongsToMany
{
return $this->belongsToMany(Country::class, 'colissimos')->withPivot('price');
}
State
Un état a plusieurs commandes :
use Illuminate\Database\Eloquent\Relations\HasMany;
...
public function orders(): HasMany
{
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
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\ { User, Address, Country, Product, Colissimo, Range, State, Shop, Page, Order };
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
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],
]);
User::factory()
->count(20)
->create()
->each(function ($user) {
$user->addresses()->createMany(
Address::factory()->count(mt_rand(2, 3))->make()->toArray()
);
});
$user = User::find(1);
$user->admin = true;
$user->save();
foreach ([
['name' => 'Montre', 'price' => 56, 'weight' => 0.3, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'montre.png', 'description' => 'Superbe montre de luxe automatique.'],
['name' => 'Lunettes', 'price' => 75, 'weight' => 0.3, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'lunettes.png', 'description' => 'Superbe paire de lunettes de soleil.'],
['name' => 'Noix', 'price' => 26, 'weight' => 1, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'noix.png', 'description' => 'Merveilleuses noix biologiques.'],
['name' => 'Pain', 'price' => 12, 'weight' => .5, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'pain.png', 'description' => 'Délicieux pain biologique.'],
['name' => 'Pc portable', 'price' => 450, 'weight' => 2, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'pc.png', 'description' => 'Superbe pc portable.'],
['name' => 'Rollers', 'price' => 1500, 'weight' => 2.6, 'active' => true, 'quantity' => 100, 'quantity_alert' => 10, 'image' => 'rollers.png', 'description' => 'Paire de rollers adaptés aux slaloms.'],
] as $productData) {
Product::create($productData);
}
Shop::factory()->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) {
Page::factory()->create([
'slug' => $item[0],
'title' => $item[1],
]);
}
Order::factory()
->count(30)
->create()
->each(function ($order) {
$address = $order->user->addresses()->take(1)->get()->makeHidden(['id', 'user_id'])->toArray();
$order->addresses()->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->addresses()->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 dont un administrateur
- 6 produits avec une image pour chacun
- 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
Si vous recueillez des erreurs, 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
Le disque public mentionné dans le fichier de configuration des systèmes de fichiers de Laravel est destiné aux fichiers qui doivent être accessibles publiquement. Par défaut, ce disque public utilise le pilote local et stocke ses fichiers dans le répertoire storage/app/public.
Pour rendre ces fichiers accessibles depuis le web, vous devez créer un lien symbolique de public/storage vers storage/app/public. En utilisant cette convention de dossiers, vous garderez vos fichiers accessibles publiquement dans un seul répertoire, ce qui facilite leur partage lors des déploiements.
Pour créer ce lien symbolique, il y a une commande artisan :
php artisan storage:link
On va donc placer toutes les images dans le dossier storage/app/public. Mais elles seront accessibles à partir de public/storage. Pour une meilleure organisation, les images seront placées dans un dossier photos :
Les images se trouvent dans le dépôt Github.
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 !
Par bestmomo
Aucun commentaire