
Shopping : les statistiques
Pour compléter le projet de boutique en ligne je vous propose dans cet article de mettre en place quelques statistiques : le nombre de commandes et de nouveaux clients. On doit pouvoir choisir l’année concernée.
Vous pouvez télécharger un ZIP du projet ici.
Un package
Il existe quelques packages pour dessiner des graphes mais celui que je préfère est Laravel Charts :
La version 7 vient tout juste d’être lancée avec un remaniement de fond en particulier le choix de Chartisan pour le frontend.
Il est facile à installer :
composer require consoletvs/charts:7.*
Si ça coince commencez par mettre à jour vos librairies (il faut aussi PHP >= 7.4) :
composer update
Contrôleur et route
On va créer un contrôleur :
php artisan make:controller Back\StatisticsController
On le rend invocable :
<?php namespace App\Http\Controllers\Back; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class StatisticsController extends Controller { public function __invoke(Request $request) { // On va coder ici } }
On ajoute une route :
Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () { ... Route::name('statistics')->get('statistiques/{year}', 'StatisticsController');
On prévoit le paramètre year pour l’année.
Le menu
On ajoute l’item dans le menu de l’administration (back.layout) avec l’année actuelle par défaut comme paramètre :
<x-menu-item :href="route('statistics', now()->year)" icon="chart-bar" :active="currentRouteActive('statistics')"> Statistiques </x-menu-item>
Les commandes
On va créer la classe pour le graphe des commandes :
php artisan make:chart OrdersChart
On l’enregistre dans AppServiceProvider :
use ConsoleTVs\Charts\Registrar as Charts; use App\Charts\OrdersChart; ... public function boot(Charts $charts) { $charts->register([ OrdersChart::class ]);
On a un code par défaut dans la classe OrdersChart mais plutôt que de tout coder dans cette classe on va en créer une autre abstraite parce qu’on va avoir du code commun avec les nouveaux utilisateurs qu’on va voir plus loin dans cet article.
On crée la classe CommonChart :
<?php namespace App\Charts; use ConsoleTVs\Charts\BaseChart; use Chartisan\PHP\Chartisan; abstract class CommonChart extends BaseChart { protected function chartisan($model, $title) { $year = request()->year; $datas = $this->datas($year, $model); return Chartisan::build() ->labels($datas->pluck('month_name')->toArray()) ->dataset($title , $datas->pluck('data')->toArray()); } protected function datas($year, $model) { return $model->selectRaw(' count(*) data, month(created_at) month, monthname(created_at) month_name ') ->whereYear('created_at', $year) ->groupBy('month', 'month_name') ->orderBy('month', 'asc') ->get(); } }
Cette classe est abstraite parce qu’elle n’a pas vocation a être instanciée.
On commence par récupérer l’année dans la requête :
$year = request()->year;
Ensuite on récupère les données :
$datas = $this->datas($year, $model);
C’est une fonction qui assure cette tâche :
protected function datas($year, $model) { return $model->selectRaw(' count(*) data, month(created_at) month, monthname(created_at) month_name ') ->whereYear('created_at', $year) ->groupBy('month', 'month_name') ->orderBy('month', 'asc') ->get(); }
On a besoin de compter les enregistrements par mois, on a aussi besoin du nom de chaque mois pour l’afficher. On classe aussi par mois.
On peut alors générer les étiquettes et le dataset pour le graphe :
return Chartisan::build() ->labels($datas->pluck('month_name')->toArray()) ->dataset($title , $datas->pluck('data')->toArray());
Du coup la classe OrdersChart est légère :
<?php declare(strict_types = 1); namespace App\Charts; use Chartisan\PHP\Chartisan; use Illuminate\Http\Request; use App\Models\Order; class OrdersChart extends CommonChart { public function handler(Request $request): Chartisan { return $this->chartisan(new Order, 'Commandes'); } }
La vue
On crée la vue :
@extends('back.layout') @section('main') <div class="container-fluid"> <div class="col-12"> <div class="card"> <div class="card-body"> <div class="form-group"> <label for="customRange1">Année :  </label> @foreach ($years as $year) <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="{{ $year }}" name="year" class="custom-control-input" value="{{ $year }}" @if($actualYear == $year) checked @endif> <label class="custom-control-label" for="{{ $year }}">{{ $year }}</label> </div> @endforeach </div> </div> </div> </div> <div class="col-12"> <div class="card"> <div id="ordersChart" style="height: 300px;" class="card-body"> </div> </div> </div> </div> @endsection @section('js') <script src="https://unpkg.com/chart.js/dist/Chart.min.js"></script> <script src="https://unpkg.com/@chartisan/chartjs/dist/chartisan_chartjs.js"></script> <script> $(function() { const OrdersChart = new Chartisan({ el: '#ordersChart', url: "@chart('orders_chart')" + '?year={{ $actualYear }}', hooks: new ChartisanHooks() .colors(['#c33']) .responsive() .beginAtZero() }); $('input').change(function() { let year = $("input[name='year']:checked").val(); let param = '?year=' + year;; OrdersChart.update({ url: "@chart('orders_chart')" + param }); window.history.replaceState('', '', '/admin/statistiques/' + year); }); }); </script> @endsection
On ajoute aussi le titre dans config.titles :
return [ ... 'statistics' => 'Statistiques',
La librairie est facile à utiliser, on peut ajuster quelques options, ici la couleur, le fait que ça commence à 0…
On met aussi à jour l’adresse pour être cohérent.
On a aussi automatiquement la création d’une route côté serveur :
C’est cette route qu’on appelle pour créer et actualiser le graphe.
On va aussi coder le contrôleur StatisticsController pour appeler et nourrir cette vue :
<?php namespace App\Http\Controllers\Back; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\Models\Order; class StatisticsController extends Controller { public function __invoke(Request $request) { $actualYear = $request->year; // Années disponibles $years = range(Order::oldest()->first()->created_at->year, now()->year); return view('back.statistics.index', compact( 'years', 'actualYear' )); } }
Et ça devrait marcher :
On peut maintenant obtenir les statistiques des commandes pour chaque année. Le seul problème c’est que les mois sont en anglais alors on va régler ça dans AppSerciveProvider :
use DB; ... public function boot(Charts $charts) { DB::statement("SET lc_time_names = 'fr_FR'");
On a maintenant les mois en français :
On a un tooltip assez élégant au survol :
Les nouveaux clients
Pour les statistiques des nouveaux clients on crée aussi une classe :
php artisan make:chart UsersChart
<?php declare(strict_types = 1); namespace App\Charts; use Chartisan\PHP\Chartisan; use Illuminate\Http\Request; use App\Models\User; class UsersChart extends CommonChart { public function handler(Request $request): Chartisan { return $this->chartisan(new User, 'Nouveaux clients'); } }
On voit ici l’intérêt de la classe CommonChart qu’on a créée précédemment.
On complète ainsi la vue back.statistics.index :
@extends('back.layout') @section('main') <div class="container-fluid"> ... <div class="col-12"> <div class="card"> <div id="usersChart" style="height: 300px;" class="card-body"> </div> </div> </div> </div> @endsection @section('js') ... const UsersChart = new Chartisan({ el: '#usersChart', url: "@chart('users_chart')" + '?year={{ $actualYear }}', hooks: new ChartisanHooks() .colors(['#3c3']) .responsive() .beginAtZero() }); $('input').change(function() { let year = $("input[name='year']:checked").val(); let param = '?year=' + year;; OrdersChart.update({ url: "@chart('orders_chart')" + param }); UsersChart.update({ url: "@chart('users_chart')" + param }); window.history.replaceState('', '', '/admin/statistiques/' + year); }); }); </script> @endsection
On a maintenant aussi les statistiques pour les nouveaux clients :
Conclusion
On a vu dans cet article qu’il est facile d’ajouter des statistiques à notre boutique, surtout avec ce package et cette librairie. Je n’ai montré qu’une approche sommaire et on peut bien améliorer cet aspect de maintes manières.

39 commentaires
softcode
Bonsoir best momo j’ai rencontré un problème au niveau de l’installation quand j’ai essayé de lancer la commande composer require consoletvs/charts:7.* j’ai eu cette erreur:
– Root composer.json requires consoletvs/charts 7.*, found consoletvs/charts[dev-main, 6.5.6, 6.6.0] but it does not match the constraint.
Use the option –with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
j’ai même mis à jour composer mais aucun succes ça ne marche pas toujours, svp vous pouvez m’aider à cela ? merci
bestmomo
Salut,
La version 7 a été abandonnée. Le développeur prépare une version 8. En attendant seule la version 6 est disponible.
DIM
Salut best , tu peux nous donner un script js qui prend en compte l’année chécké , et qui met à jour les données par rapport à cette année . mais avec la bibliothèque chartjs de laravel-charts .
vu que la bibliothèque chartisan n’existe plus .
en resumé le meme travail que ce projet ci mais cette fois avec la bibliothèque chartjs
Merci
bestmomo
Salut,
En ce moment je suis en vacances avec juste un téléphone alors pas trop de codage.
DIM
Salut , je te soihaite de passer de passer de très bonnes vacances.
best j’aimerai refaire la procédure pour avoir les statistiques par an , le problème est qu’ avec le composant menu-item.blade.php que nous avons créer dans le projet du blog , je n’arrive à prendre en compte le paramètre year , il est dit que le paramètre est manquant , ça se presente comme ceci:
back.menu-item
‘role’ => ‘user’,
‘route’ => « ‘statistics’, now()->year »,
‘icon’=> ‘chart-bar’,
on me dit que le paramètre year est manquant
la route est invokable
Route::get(‘admin/statistiques/{year}’, StatisticsController::class)->name(‘statistics’);
que dois-je améliorer dans mon composant pour prendre en compte l’id ?
merci et désolé vraiment de te pousser à réfléchir sur le code .
bestmomo
Salut,
Je ne comprends pas comment tu veux gérer les années.
DIM
salut , alors premièrement je récupère les années comprises entre la plus vieille et l’année en cours, ensuite je les affiche dans des boutons radio , au clic sur une année je souhaite n’obtenir que les statistiques de l’année en question. mais j’aimerai qu’au chargement de la page récupérer les années de l’année en cours , d’où le now()->year que j’éssaie d’introduire sans succès en me basant sur le composant du menu-item.
Best j’ai aussi éssayé ce script pour la librairie chartjs de laravel-chart pour éssayer de mettre à jour les résultats sur le clic d’une année.
script
$(function() {
$(‘input’).change(function() {
let year = $(« input[name=’year’]:checked »).val();
let param = ‘?year=’ + year;
var url = {{ $maleChart->id }}_api_url;
{{$maleChart->id}}_refresh(
url + param);
window.history.replaceState( », », ‘/admin/statistiques/’ + year);
});
});
script.
je n’ai pas encore un niveau acceptable en javascript , j’ai vraiment besoin de ton aide .
Merci et bien de choses à toi.
bestmomo
J’ai un peu relu l’article et le code. Pour le chargement de l’année en cours pourquoi ne pas utiliser le code de l’article qui fonctionne bien ? Dans le composant menu-item il y a la propriété href qui définit le lien.
En ce qui concerne la bibliothèque Laravel Charts j’ai regardé dans le projet du créateur et j’ai vu qu’il a tout changé parce qu’il a dû supprimer la version 7 que j’avais utilisée. Du coup le code de l’article ne correspond plus du tout pour la création des graphiques. Il faut repartir sur la nouvelle version ou sut autre chose…
DIM
bestmomo salut, j’ai une erreur à ce niveau
use ConsoleTVs\Charts\Registrar il est dit que cette classe n’existe pas et après quelques recherches j’ai appris que la version 7 qui utilisait cette classe a été annulé comment la remplacer dans ma version 6.6.0 ?
merci
bestmomo
Salut,
Oui, j’avais vu que la librairie avait disparu et je n’ai pas encore regardé comment la remplacer. Il faut totalement revoir l’installation.
jondelweb
Salut Best Momo
(C’est encore moi! )
J’arrive au bout mais j’ai encore un petit problème de taille !
J’ai cette erreur :
Call to undefined method App\Charts\OrdersChart::__set_state()
Et du coup ben je peux plus lancer mon appli…
Cette erreur ce trouve dans le fichier : routes-v7.php
Je précise que même si je fais un « composer dumpautoload » il n’y a rien qui change…
Une idée ?
jondelweb
Alors,
J’ai bien entendu fini par trouver !
En fait, ça ne fonctionne pas lorsque les routes sont en cache avec Charts… Probablement pcq on utilise le cdn et pas le package, non ?
Et il faut rajouter « umd » à la fin du cdn :
https://unpkg.com/@chartisan/chartjs/dist/chartisan_chartjs.umd.js
Sinon le chart ne s’affiche pas ! 😉
bestmomo
Salut,
Pour les routes il n’y a aucun rapport avec le CDN. Il faudra que je regarde ça mais là je suis sur la plage, c’est pas facile 🙂
jondelweb
@BestMomo
Pas de souci ! Profite bien ! 🙂
Et super taf, merci encore !
Linedev
Merci pour ce tuto super, j’ai réussi a crée un site E-commerce sans problème grâce à ton tuto !!!
Donc je te remercie pour ce travail que tu as fournie.
Ferfalam
Bonsoir BESTMOMO. Merci pour ce tutoriel c’est super instructif j’adore. Encore Merci. Je sais pas si c’est un oublie ou tu veux nous laisser gérer par nous même mais dans le dashboard quand on appuie sur appuie sur plus d’informations rien ne se passe cette partie du site n’a pas été faite. Merci de le voir je t’e prie.
bestmomo
Salut,
A quel endroit exactement se situe ce lien ?
Ferfalam
Au niveau de back/layout le text « plus d’information »
bestmomo
Ah oui il manque les routes !
href="{{ route("orders.index") }}"
href="{{ route("clients.index") }}"
fgb
Bonjour et merci pour le tuto.
je suis un novice et j’ai essayé de charger le projet du tuto pour bien comprendre et je tombe sur cette erreur:
*********
λ php artisan migrate
Illuminate\Database\QueryException
SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘forge.shops’ doesn’t exist (SQL: select * from `shops` limit 1)
at C:\laragon\www\shopping23\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|
• A table was not found: You might have forgotten to run your migrations. You can run your migrations using `php artisan migrate`.
https://laravel.com/docs/master/migrations#running-migrations
1 [internal]:0
Illuminate\Foundation\Application::Illuminate\Foundation\{closure}(Object(App\Providers\AppServiceProvider))
2 C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Database\Connection.php:331
PDOException::(« SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘forge.shops’ doesn’t exist »)
C:\laragon\www\shopping23
*****************
pouver vous m’orienter?
bestmomo
Bonjour,
Apparemment la table shops n’existe pas, est-ce que les migrations ont été faite ?
fgb
j’obtiens l’erreur quand je lance la commande pour la migration
bestmomo
Ah oui j’avais mal lu. Il faut ajouter ce code au début de la méthode boot de AppServiceProvider :
if(app()->runningInConsole()) {
return;
}
fgb
Merci, voici la nouvelle erreur:
λ php artisan migrate
Illuminate\Contracts\Container\BindingResolutionException
Target [Illuminate\Contracts\Auth\Access\Gate] is not instantiable.
at C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:1013
1009| } else {
1010| $message = « Target [$concrete] is not instantiable. »;
1011| }
1012|
> 1013| throw new BindingResolutionException($message);
1014| }
1015|
1016| /**
1017| * Throw an exception for an unresolvable primitive.
1 C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:814
Illuminate\Container\Container::notInstantiable(« Illuminate\Contracts\Auth\Access\Gate »)
2 C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:687
Illuminate\Container\Container::build(« Illuminate\Contracts\Auth\Access\Gate »)
bestmomo
On avance…
Lance la commande
composer dumpautoload
dans la consolefgb
λ composer dumpautoload
Generating optimized autoload files> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover –ansi
Illuminate\Contracts\Container\BindingResolutionException
Target [Illuminate\Contracts\Auth\Access\Gate] is not instantiable.
at C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:1013
1009| } else {
1010| $message = « Target [$concrete] is not instantiable. »;
1011| }
1012|
> 1013| throw new BindingResolutionException($message);
1014| }
1015|
1016| /**
1017| * Throw an exception for an unresolvable primitive.
1 C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:814
Illuminate\Container\Container::notInstantiable(« Illuminate\Contracts\Auth\Access\Gate »)
2 C:\laragon\www\shopping23\vendor\laravel\framework\src\Illuminate\Container\Container.php:687
Illuminate\Container\Container::build(« Illuminate\Contracts\Auth\Access\Gate »)
Script @php artisan package:discover –ansi handling the post-autoload-dump event returned with error code 1
bestmomo
C’est vraiment bizarre, le mieux serait peut-être de réinstaller Laravel.
fgb
Merci,
j’ai reinstallé ,
ajusté
if(app()->runningInConsole()) {
return;
}
et la migration c’est bien passée!
Lerado
J’ai rencontré des soucis avec Charts, surtout au niveau des cdn et de leur compatibilité avec le script plus bas. Je conseille de regarder ceci sur la doc officielle https://chartisan.dev/documentation/building#Using-the-CDNs
kopatik
Merci !!
Ca bugué chez moi aussi, mais maintenant c’est réglé 🙂
kopatik
Je me permets de vous linker l’adresse de mon shop réalisé avec ce tuto.
J’ai ajouté les catégories aux produits, le paiement par paypal, la visualisation des notifs du dashboard, une adresse principale par client…
Merci encore pour le travail effectué avec ce tuto, vraiment !! 🙂
Lien vers KopaShop : https://kopachic.fgainza.fr
Lien GitHub : https://github.com/FredGainza/kopachic-laravel-online-shop
bestmomo
Ça fait plaisir de voir que quelqu’un l’adapte et le fait évoluer 😉
J’ai mis à jour le lien du commentaire 😉
Nanourgo
Bonjour !!
Je suis un débutant au cours de Laravel et ce tuto m’aide vraiment. Cependant je rencontre un problème au niveau de la commande. Lorsque j’accède à mon panier et que je clic sur le bouton COMMANDER, j’ai cette érreur
Trying to get property ‘pivot’ of non-object
que je n’arrive pas à résoudre malgré mes recherches.
bestmomo
Bonjour,
Il faut que le client ait des adresses enregistrées, comme le tuto est progressif il faut utiliser le seeder pour remplir la base.
Julienmivo
Bonjour Bestmomo,
J’ai une erreur que je ne comprends pas « Target class [ConsoleTVs\Charts\Registrar] does not exist. »
Merci pour tout ce que tu fais
bestmomo
Logiquement le package est reconnu automatiquement et donc ses classes utilisables. Essaie un composer dumpautoload pour régénérer le chargement automatique des classes.
kopatik
Merci pour cette belle série de tutos, je l’ai suivie depuis le début et tout fonctionne !!
Vraiment appréciable d’avoir des tutos de cette qualité en français.
PS : ne pas oublier d’enregistrer dans AppServiceProvider la classe du graphe des utilisateurs, UsersChart 😉
Julienmivo
Merci
bestmomo
Ça complète bien le projet 🙂