Laravel 8

Créer un blog – la page d’accueil

Nous avons dans le précédent article mis en place les migrations essentielles ainsi que la population. Maintenant que nous avons du contenu nous allons pouvoir passer au contenant. Dans un premier temps nous allons installer un package pour la gestion des medias. Ensuite on utilisera le thème Calvin en l’adaptant à Laravel pour créer notre page d’accueil. Pour rappel le code de départ du présent article est le code final du précédent que vous pouvez télécharger en y retournant.

Vous pouvez télécharger le code final de cet article ici.

Les medias

Pour la gestion des medias on va utiliser l’excellent package Laravel File Manager. On l’installe :

composer require unisharp/laravel-filemanager

Ensuite on publie la configuration est les assets :

php artisan vendor:publish --tag=lfm_config
php artisan vendor:publish --tag=lfm_public

On crée un lien symbolique :

php artisan storage:link

Ainsi le dossier public\storage est relié au dossier storage\app\public.

Pour terminer on ajoute les routes dans routes/web.php :

use UniSharp\LaravelFilemanager\Lfm;

Route::group(['prefix' => 'laravel-filemanager', 'middleware' => 'auth'], function () {
    Lfm::routes();
});

Vous pouvez maintenant accéder à la démo en vous connectant (puisqu’on a prévu le middleware auth). Puisqu’on a installé Breeze vous pouvez accéder à la page de connexion avec l’url monblog.ext/login. (ext représente l’extension que vous utilisez pour votre développement). Comme on a créé des utilisateurs dans le précédent article vous pouvez aller trouver un email dans la base de données, tous les mots de passe sont password.

La démo est accessible à l’adresse monblog.ext/laravel-filemanager/demo :

Si ça ne fonctionne pas chez vous reprenez la procédure d’installation.

Par défaut vous avez ces dossiers de créés :

Si vous vous connectez avec l’utilisateur qui a l’id 2 et que vous chargez une image vous aller voir la création de ces dossiers :

Le principe est simple : les images sont toutes dans le dossier photos. Chaque utilisateur a son dossier dont le nom est son identifiant. On a deux versions de l’image : une réduite dans thumbs et une normale. On a les réglages pour l’image réduite dans la configuration (config/lfm.php) :

/*
|--------------------------------------------------------------------------
| Thumbnail
|--------------------------------------------------------------------------
  */

// If true, image thumbnails would be created during upload
'should_create_thumbnails' => true,

'thumb_folder_name'        => 'thumbs',

// Create thumbnails automatically only for listed types.
'raster_mimetypes'         => [
    'image/jpeg',
    'image/pjpeg',
    'image/png',
],

'thumb_img_width'          => 200, // px

'thumb_img_height'         => 200, // px

On va conserver ces réglages. Pour notre page d’accueil j’ai prévu des images que vous pouvez aller récupérer dans le fichier ZIP à télécharger. Il vous suffit de copier le dossier photos et son contenu :

Vous aurez ainsi les images pour les 9 articles qu’on a déjà créés.

Calvin

Vous avez normalement téléchargé le thème Calvin et vous devez disposer de tout ça :

On ne va évidemment pas tout prendre. On aura besoin des 2 fichiers CSS :

Vous allez donc les copier dans public/css :

Le fichier app.css correspond aux règles de style de Breeze, on le supprimera quand on aura basculé les vues de l’authentification avec nos nouvelles versions.

On va aussi avoir besoin de fichiers Javascript :

On les copie dans notre dossier :

Le fichier app.jss correspond aussi à Breeze, on le supprimera également quand on aura basculé.

En ce qui concerne JQuery on utilisera un CDN, ça sera plus efficace. Il est aussi possible qu’à la fin on condense tout ça dans un seul fichier.

Vous avez aussi dans Calvin les fichiers suivants :

Les icônes représentent un « C » qui est l’initiale de Calvin. J’ai créé les fichiers équivalents avec un « B », vous pouvez les récupérer dans le dossier ZIP. Copiez-les dans notre dossier public.

Pour terminer on va récupérer le fichier index.html, le renommer layout.blade.php, créer un dossier views/front et enregistrer le fichier dans ce dossier :

Il va falloir évidemment lui apporter pas mal de modifications…

Un contrôleur et un repository pour les articles

On a déjà un contrôleur PostController qu’on a créé précédemment en même temps que le modèle et les migrations :

Pour bien séparer avec le backend on va créer un dossier Front et le mettre dedans (attention au changement d’espace de nom) :

Comme on aura pas mal de manipulations au niveau de la base de données on va aussi prévoir un repository :

Pour définir le nombre d’articles à afficher sur chaque page on prévoit une information dans le fichier config.app :

/*
|--------------------------------------------------------------------------
| Pagination Configuration
|--------------------------------------------------------------------------
*/

'nbrPages' => [
    'posts' => 6,
],

On code le repository

Au niveau du repository on va avoir besoin :

  • des articles actifs classés par date paginés
  • des informations des derniers articles pour le diaporama (en langage Calvin ça s’appelle des heros)

Les articles paginés

On va commencer par les articles classés et paginés :

<?php

namespace App\Repositories;
use App\Models\Post;

class PostRepository
{
    protected function queryActive()
    {
        return Post::select(
                      'id',
                      'slug',
                      'image',
                      'title',
                      'excerpt',
                      'user_id')
                    ->with('user:id,name')
                    ->whereActive(true);
    }

    protected function queryActiveOrderByDate()
    {
        return $this->queryActive()->latest();
    }

    public function getActiveOrderByDate($nbrPages)
    {
        return $this->queryActiveOrderByDate()->paginate($nbrPages);
    }
}

J’ai séparé en 3 fonctions parce qu’on aura besoin de ce découpage pour les autres fonctionnalités. La fonction d’entrée est getActiveOrderByDate. On lui transmet le nombre de pages et elle renvoie les articles concernés. On utilise un SELECT pour éviter de charger le contenu des articles qui peut être volumineux et qui est inutile pour la page d’accueil. On charge aussi (eager loading) pour chaque article le nom de son auteur pour l’afficher).

Les heros

Les heros sont les 5 derniers articles créés ou modifiés, on ajoute donc cette fonction :

public function getHeros()
{
    return $this->queryActive()->with('categories')->latest('updated_at')->take(5)->get();
}

On charge aussi les catégories parce qu’on doit les afficher.

On code le contrôleur et la route

Le contrôleur va utiliser le repository :

<?php

namespace App\Http\Controllers\Front;
use App\Repositories\PostRepository;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class PostController extends Controller
{
    protected $postRepository;
    protected $nbrPages;

    public function __construct(PostRepository $postRepository)
    {
        $this->postRepository = $postRepository;
        $this->nbrPages = config('app.nbrPages.posts');
    }

    public function index()
    {
        $posts = $this->postRepository->getActiveOrderByDate($this->nbrPages);
        $heros = $this->postRepository->getHeros();

        return view('front.index', compact('posts', 'heros'));
    }
}

Pour la route on supprime celle de Breeze :

Route::get('/', function () {
    return view('welcome');
});

Et on crée la nôtre pour pointer sur le contrôleur :

use App\Http\Controllers\Front\PostController as FrontPostController;

Route::name('home')->get('/', [FrontPostController::class, 'index']);

Le layout

C’est dans les vues qu’on va avoir le plus de travail. Récupérez le fichier index.html de Calvin et copiez-le ici en changeant son nom et en créant le dossier front :

On va traiter ce layout par zones.

Le head

On a ce code :

<!DOCTYPE html>
<html class="no-js" lang="en">
<head>

    <!--- basic page needs
    ================================================== -->
    <meta charset="utf-8">
    <title>Calvin</title>
    <meta name="description" content="">
    <meta name="author" content="">

    <!-- mobile specific metas
    ================================================== -->
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSS
    ================================================== -->
    <link rel="stylesheet" href="css/vendor.css">
    <link rel="stylesheet" href="css/styles.css">

    <!-- script
    ================================================== -->
    <script src="js/modernizr.js"></script>
    <script defer src="js/fontawesome/all.min.js"></script>

    <!-- favicons
    ================================================== -->
    <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
    <link rel="manifest" href="site.webmanifest">

</head>

On va renseigner la langue selon la locale :

<html class="no-js" lang="{{ str_replace('_', '-', app()->getLocale()) }}">

Pour le titre on va aller le chercher dans la configuration :

<title>{{ config('app.name') }}</title>

Pour le CSS on utilise l’helper pour générer l’url complète et on prévoit un emplacement pour les les vues qui vont utiliser ce layout puissent ajouter du style :

<link rel="stylesheet" href="{{ asset('css/vendor.css') }}">
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
@yield('style')

Pour le Javascript on utilise aussi l’helper :

<script src="{{ asset('js/modernizr.js') }}"></script>
<script defer src="{{ asset('js/fontawesome/all.min.js') }}"></script>

Pareil pour les icônes :

<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('apple-touch-icon.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-16x16.png') }}">
<link rel="manifest" href="{{ asset('site.webmanifest') }}">

Le header

Le header contient la barre de navigation :

<header class="s-header">

    <div class="s-header__logo">
        <a class="logo" href="index.html">
            <img src="images/logo.svg" alt="Homepage">
        </a>
    </div>

    <div class="row s-header__navigation">

        <nav class="s-header__nav-wrap">

            <h3 class="s-header__nav-heading h6">Navigate to</h3>

            <ul class="s-header__nav">
                ...
            </ul> <!-- end s-header__nav -->

            <a href="#0" title="Close Menu" class="s-header__overlay-close close-mobile-menu">Close</a>

        </nav> <!-- end s-header__nav-wrap -->

    </div> <!-- end s-header__navigation -->

    <a class="s-header__toggle-menu" href="#0" title="Menu"><span>Menu</span></a>

    <div class="s-header__search">

        <div class="s-header__search-inner">
            <div class="row wide">

                <form role="search" method="get" class="s-header__search-form" action="#">
                    ...
                </form>

                <a href="#0" title="Close Search" class="s-header__overlay-close">Close</a>

            </div> <!-- end row -->
        </div> <!-- s-header__search-inner -->

    </div> <!-- end s-header__search wrap -->	

    <a class="s-header__search-trigger" href="#">
        ...
    </a>

</header>

Qui correspond à cette zone :

Il va déjà falloir changer le logo, j’en ai créé un que vous pouvez trouver dans le fichier ZIP, copiez-le ici :

Dans le code on renseigne l’url de la page d’accueil et on utilise l’helper pour l’url du logo :

<div class="s-header__logo">
    <a class="logo" href="{{ route('home') }}">
        <img src="{{ asset('images/logo.svg') }}" alt="Homepage">
    </a>
</div>

On s’occupera du menu plus loin, on va donc conserver le reste du code inchangé.

La zone hero

La zone suivante est celle du diaporama (les heros).

On y trouve donc le diaporama mais également des icônes sociales. On va ici se contenter de prévoir un emplacement qu’on remplira avec la vue index :

<!-- hero
================================================== -->
@yield('hero')

Le content

C’est là qu’on a le résumé des articles :

On va aussi juste prévoir un emplacement :

<!-- content
================================================== -->
<section class="s-content s-content--no-top-padding">

    @yield('main')

</section>

Le footer

Pour le moment on ne va rien faire dans le footer et se contenter de le conserver tel quel.

Le Javascript

Pour le Javascript en bas de page on va utiliser un CDN pour JQuery et l’helper pour les urls des fichiers. On va aussi ajouter un emplacement pour pouvoir ajouter du code dans les vues enfants :

<!-- JavaScript
================================================== -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="{{ asset('js/plugins.js') }}"></script>
<script src="{{ asset('js/main.js') }}"></script>
@yield('scripts')

Un composeur de vue

Comme on aura besoin d’informations systématiques dans le layout, mais aussi dans la vue index on va créer un composeur de vue :

Avec ce code :

<?php

namespace App\Http\ViewComposers;

use Illuminate\View\View;
use App\Models\Category;

class HomeComposer
{
    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with([
            'categories' => Category::has('posts')->get(),
        ]);
    }
}

On envoie dans la vue les catégories qui possèdent des articles. Il n’existe à ma connaissance pas de commande Artisan pour générer ces classes.

Il faut ensuite utiliser ce composeur de vue dans AppServiceProvider :

use App\Http\ViewComposers\HomeComposer;
use Illuminate\Support\Facades\View;

...

public function boot()
{
    View::composer(['front.layout', 'front.index'], HomeComposer::class);
}

Maintenant on est sûr d’avoir les catégories dans ces deux vues.

Le vue index

Maintenant qu’on a un layout présentable on va créer la vue index :

 

On va avoir cette structure :

@extends('front.layout')


@section('hero')

@endsection


@section('main')

@endsection

Les heros

On va s’occuper du diaporama. On a vu qu’on envoie les données à partir du contrôleur et qu’on dispose ainsi d’une variable $heros.

Un composant

Comme on va avoir du code répétitif on crée un composant anonyme :

Avec ce code :

@props(['post'])

<div class="s-hero__slide">

  <div class="s-hero__slide-bg" style="background-image: url('storage/photos/{{ $post->user->id }}/{{ $post->image }}')"></div>

  <div class="row s-hero__slide-content animate-this">
      <div class="column">
          <div class="s-hero__slide-meta">
              <span class="cat-links">
                  @foreach($post->categories as $category)
                      <a href="#">{{ $category->title }}</a>
                  @endforeach
              </span>
              <span class="byline"> 
                  @lang('Posted By') 
                  <span class="author">
                      <a href="#">{{ $post->user->name }}</a>
                  </span>
              </span>
          </div>
          <h1 class="s-hero__slide-text">
              <a href="#">{{ $post->title }}</a>
          </h1>
      </div>
  </div>

</div>

Pour l’instant on ne peut pas renseigner les liens mais ça viendra…

Les heros dans la vue

On peut maintenant intégrer les heros dans la vue index :

@section('hero')

    @isset($heros)

        <section id="hero" class="s-hero">

          <div class="s-hero__slider">

              @foreach($heros as $hero)
                  <x-front.hero :post="$hero" />
              @endforeach

          </div>

          <div class="s-hero__social hide-on-mobile-small">
              <p>@lang('Follow')</p>
              <span></span>
              <ul class="s-hero__social-icons">
                  <li><a href="#0"><i class="fab fa-facebook-f" aria-hidden="true"></i></a></li>
                  <li><a href="#0"><i class="fab fa-twitter" aria-hidden="true"></i></a></li>
                  <li><a href="#0"><i class="fab fa-instagram" aria-hidden="true"></i></a></li>
                  <li><a href="#0"><i class="fab fa-dribbble" aria-hidden="true"></i></a></li>
              </ul>
          </div>

          <div class="nav-arrows s-hero__nav-arrows">
              <button class="s-hero__arrow-prev">
                  <svg viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M1.5 7.5l4-4m-4 4l4 4m-4-4H14" stroke="currentColor"></path></svg>
              </button>
              <button class="s-hero__arrow-next">
                <svg viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M13.5 7.5l-4-4m4 4l-4 4m4-4H1" stroke="currentColor"></path></svg>
              </button>
          </div>

        </section>

    @endisset

@endsection

Tous les heros sont envoyés dans le composant qu’on a créé avec ce code :

@foreach($heros as $hero)
    <x-front.hero :post="$hero" />
@endforeach

Si vous avez bien travaillé ça doit fonctionner avec le diaporama :

Les articles

Un helper

Pour chaque article de la page d’accueil on va devoir aller récupérer l’image réduite (thumb). On va se créer un petit helper pour le faire. Comme on n’a pas encore de fichier pour les helpers on va en créer un :

Pour que les fonction qu’on va ajouter dans ce fichier soient connues il faut informer l’autoloader dans composer.json :

"autoload": {
    "psr-4": {
        ...
    },
    "files": [
        "app/helpers.php"
    ]
},

On crée la fonction pour aller chercher une image :

<?php

if (!function_exists('getImage')) {
    function getImage($post, $thumb = false)
    {   
        $url = "storage/photos/{$post->user->id}";
        if($thumb) $url .= '/thumbs';
        return asset("{$url}/{$post->image}");
    }
}

Pour que ce soit vraiment prise en compte il faut raffraîchir l’autoload :

composer dumpautoload

Les bricks

On va avoir des pavés (dans Calvin ça s’appelle des bricks). Là encore on va créer un composant parce que le code est répétitif :

Avec ce code :

@props(['post'])

<article class="brick entry" data-aos="fade-up">

  <div class="entry__thumb">
      <a href="#" class="thumb-link">
          <img src="{{ getImage($post, true) }}" alt="" style="width:100%">
      </a>
  </div>

  <div class="entry__text">
      <div class="entry__header">
          <h1 class="entry__title"><a href="#">{{ $post->title }}</a></h1>          
          <div class="entry__meta">
              <span class="byline"">@lang('By:')
                  <span class='author'>
                      <a href="#">{{ $post->user->name }}</a>
                  </span>
              </span>
          </div>
      </div>
      <div class="entry__excerpt">
          <p>{{ $post->excerpt }}</p>
      </div>
      <a class="entry__more-link" href="#">@lang('Read More')</a>
  </div>

</article>

Les bricks dans la vue

On peut maintenant intégrer les bricks dans la vue index :

@section('main')

    @isset($title)
        <div class="row">
            <div class="column">
                <h1>{!! $title !!}</h1>
            </div>
        </div>
    @endisset

    <div class="s-bricks">

      <div class="masonry">
          <div class="bricks-wrapper h-group">

              <div class="grid-sizer"></div>

              <div class="lines">
                  <span></span>
                  <span></span>
                  <span></span>
              </div>

              @foreach($posts as $post)

                  <x-front.brick :post="$post" />

              @endforeach

            </div>

      </div>

      <div class="row">
          <div class="column large-12">
              {{-- On va devoir s'occuper de la pagination ici --}}
          </div>
      </div>

  </div>

@endsection

La pagination ne fonctionnera pas encore mais vous devriez avoir les bricks :

La pagination

La pagination dans Laravel est prévue pour Bootstrap et Tailwind mais évidemment pas pour Calvin ! Alors on va devoir la créer…

Créez une nouvelle vue pour cette pagination :

Avec ce code :

@if ($paginator->hasPages())
    <nav class="pgn">
        <ul>
            {{-- Previous Page Link --}}
            <li>
                @if ($paginator->onFirstPage())
                    <span class="pgn__prev inactive" href="#0">@lang('Prev')</span>
                @else
                    <a href="{{ $paginator->previousPageUrl() }}" class="pgn__prev" rel="prev">@lang('Prev')</a>
                @endif
            </li>

            {{-- Pagination Elements --}}
            @foreach ($elements as $element)

                {{-- "Three Dots" Separator --}}
                @if (is_string($element))
                    <li><span class="pgn__num current">{{ $element }}</span></li>
                @endif

                {{-- Array Of Links --}}
                @if (is_array($element))
                    @foreach ($element as $page => $url)
                        <li>
                            @if ($page == $paginator->currentPage())
                                <span class="pgn__num current">{{ $page }}</span>
                            @else
                                <a href="{{ $url }}" class="pgn__num">{{ $page }}</a>
                            @endif
                        </li>
                    @endforeach
                @endif

            @endforeach

            {{-- Next Page Link --}}
            <li>
                @if ($paginator->hasMorePages())
                    <a href="{{ $paginator->nextPageUrl() }}"  class="pgn__next" rel="next">@lang('Next')</a>
                @else
                    <span class="pgn__next inactive">@lang('Next')</span>
                @endif
            </li>
        </ul>
    </nav>
@endif

Dans la vue index modifiez ainsi le code de la zone pour la pagination :

<div class="row">
    <div class="column large-12">
        {{ $posts->links('front.pagination') }}
    </div>
</div>

Et là si tout se passe bien :

On a enfin une page d’accueil qui fonctionne !

Les menu

Il est temps maintenant de s’occuper un peu du menu…

On va ajouter un helper (dans le fichier helpers) pour repérer la route active et placer la bonne classe :

if (!function_exists('currentRoute')) {
    function currentRoute($route)
    {
        return Route::currentRouteNamed($route) ? ' class=current' : '';
    }
}

Pour le menu on va se contenter pour le moment du lien pour la page d’accueil et du sous-menu des catégories. Je montre là tout le header :

<header class="s-header">

    <div class="s-header__logo">
        <a class="logo" href="{{ route('home') }}">
            <img src="{{ asset('images/logo.svg') }}" alt="Homepage">
        </a>
    </div>

    <div class="row s-header__navigation">

        <nav class="s-header__nav-wrap">

            <h3 class="s-header__nav-heading h6">@lang('Navigate to')</h3>

            <ul class="s-header__nav">
                <li {{ currentRoute('home') }}>
                    <a href="{{ route('home') }}" title="">@lang('Home')</a>
                </li>
                <li class="has-children">
                    <a href="#" title="">@lang('Categories')</a>
                    <ul class="sub-menu">
                        @foreach($categories as $category)
                            <li><a href="#">{{ $category->title }}</a></li>
                        @endforeach
                    </ul>
                </li>
            </ul>

            <a href="#0" title="@lang('Close Menu')" class="s-header__overlay-close close-mobile-menu">@lang('Close')</a>

        </nav>

    </div>

    <a class="s-header__toggle-menu" href="#0" title="@lang('Menu')"><span>@lang('Menu')</span></a>

    <div class="s-header__search">

        <div class="s-header__search-inner">
            <div class="row wide">

                <form role="search" method="get" class="s-header__search-form" action="#">
                    <label>
                        <span class="h-screen-reader-text">@lang('Search for:')</span>
                        <input type="search" class="s-header__search-field" placeholder="Search for..." value="" name="s" title="Search for:" autocomplete="off">
                    </label>
                    <input type="submit" class="s-header__search-submit" value="Search"> 
                </form>

                <a href="#0" title="@lang('Close Search')" class="s-header__overlay-close">@lang('Close')</a>

            </div>
        </div>

    </div>

    <a class="s-header__search-trigger" href="#">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.982 17.983"><path fill="#010101" d="M12.622 13.611l-.209.163A7.607 7.607 0 017.7 15.399C3.454 15.399 0 11.945 0 7.7 0 3.454 3.454 0 7.7 0c4.245 0 7.699 3.454 7.699 7.7a7.613 7.613 0 01-1.626 4.714l-.163.209 4.372 4.371-.989.989-4.371-4.372zM7.7 1.399a6.307 6.307 0 00-6.3 6.3A6.307 6.307 0 007.7 14c3.473 0 6.3-2.827 6.3-6.3a6.308 6.308 0 00-6.3-6.301z"/></svg>
    </a>

</header>

J’en ai profité pour localiser quelques textes.

On a maintenant un début de menu :

Conclusion

On a désormais une page d’accueil fonctionnelle, même s’il faudra encore y revenir pour plusieurs raisons (compléter le menu, ajouter des liens, adapter les liens sociaux, modifier le bas de page…).

Dans le prochain article on s’occupera de l’affichage des articles. On verra également l’affichage par catégorie, par auteur, par étiquette. On codera aussi la recherche.

Print Friendly, PDF & Email

80 commentaires

  • Romain

    Je re-recommance un projet à zero, et ça déconne encore à un nouvel endroit.

    J’ai cette ligne de code qui ne marche pas :
    return $this->queryActive()->with(‘categories’)->latest(‘updated_at’)->take(5)->get();
    Elle se trouve dans le fichier PostRepository en tant que fonction getHeros().

    ça m’indique comme erreur que laravel ne trouve pas la table nommée « Article_Category ». Ce qui est normal puisqu’elle se nomme « Category_Article ». Mais je ne trouve nulle part où j’aurais demandé de chercher la table sous ce nom erroné.

    Pour faire fonctionner le code, je dois remplacer ladite fonction par celle-ci :
    return $this->queryActive()/*->with(‘categories’)*/->latest(‘updated_at’)->take(5)->get();
    C’est donc bien un problème avec le code pur de laravel, et non pas une erreur de ma part. Je doute qu’il y ait moyen de lui faire comprendre comment se nomme réellement la table.

    • bestmomo

      Salut,

      La table pivot entre les catégories et les articles s’appelle category_post dans ce qui a été défini dans le premier article de cette série sur la réalisation d’un blog avec les données. Comment se fait-il que tu aies une table qui s’appelle Category_Article ? Si tu as renommé la table posts en articles alors la table pivot doit s’appeler article_category selon les principes de nommage de Laravel. Si ce n’est pas le cas et que tu as une table nommée category_articles alors, il faut informer Laravel de cette particularité dans la définition de la relation :
      return $this->belongsToMany(Article::class, 'category_articles');

      • Romain

        Merci bien 😉
        J’ai dû mettre ce petit ajout dans le modèle Article pour que ça marche et non pas dans le Category (bien que ça n’occasionne pas de problème de l’y mettre aussi). Et j’ai dû le mettre au singulier.
        Du coup, je prendrai pour habitude de toujours spécifier le nom de la table pivot dans les deux modèles pour être sûr qu’il n’y ait plus de problèmes.

          • Romain

            Excellent 🙂
            Je ferai une seconde fois le blog alors 😉

            Il me semble que Fortify est plus pratique pour avoir un projet personnalisé, et j’avais trouvé Angular extrêmement puissant. Penses-tu qu’il pourrait être intéressant de faire le cours du blog avec ces technologies ?

          • bestmomo

            Ce blog sera plus performant et élégant que le précédant 😉 Et tu peux participer au développement !

            En ce qui concerne Fortify c’est la couche de sécurité qui se situe entre le backend de Laravel et le frontend. Il est intéressant de l’utiliser directement si on veut bien personnaliser l’apparence de l’authentification. J’avais un peu développé cet aspect dans cet article un peu ancien maintenant. Mais maintenant, avec Breeze et en adaptant les vues, je n’en ai jamais eu besoin.

            Pour Angular, je l’avais utilisé il y a quelques années et c’est vraiment performant. Mais maintenant que Laravel s’est équipé de Livewire, on consacre moins de temps et de ressources au niveau du frontend et on oublie un peu ces technologies et on va aussi finir par oublier le Javascript !

  • Romain

    Il cherche un fichier index.php qui n’existait pas (et que j’ai dû créer et laisser vide) :
    InvalidArgumentException: View [front.index] not found. in file G:\laragon\www\blog\vendor\laravel\framework\src\Illuminate\View\FileViewFinder.php on line 137

  • Biggilv5

    Bonjour Best
    je débute en laravel et ton site il est vraiment super.
    j’ai un soucis avec la page d’acceuil les images ne s’affichent pas pourtant j’ai tout fait comme il faut.

    Mais j’ai un doute: le dossier photos il est dans public.storage ou dans storage tout cours ??

    • bestmomo

      Salut !
      Par défaut, le système de stockage de Laravel utilise un disque public qui est le seul accessible pour le visiteur. Mais avec le driver local les fichiers sont placés dans storage/app/public, donc non accessibles. C’est pour ça qu’on crée un lien symbolique entre public/storage et storage/app/public, comme ça ils deviennent accessibles aux visiteurs. L’intérêt de cette disposition ne saute pas aux yeux et réside dans la facilité de déploiement avec certains outils. Personnellement, je n’utilise pas ça quand je fais un projet personnel, mais là Laravel File Manager l’impose.

      Donc pour répondre à ta question, les fichiers sont placés dans storage/app/public, mais on y accède par public/storage grâce au lien symbolique.

      • Biggilv5

        Merci !

        Mais les images ne s’affichent toujours pas . J’ai vérifié avec un dd et les données sont bien transférées à la vue. Le dossier photos est dans storage/app/public comme tu as dis. Les categories s’affichent les noms d’auteurs aussi il n’y a que les images qui s’affichent pas

    • bestmomo

      Bonjour,

      Les causes peuvent être multiples. Déjà est-ce que filemanager est bien installé avec une démo qui fonctionne ? D’autre part il faudrait voir avec les outils de développement du navigateur les urls générées pour les images.

  • noubis

    salut bestmom. merci pour le tuto.
    jai suivi toues etapes jusqu’a present mais le probleme que vient des packages NESTEDSET ET FILEMANAGER
    tous les 2 viennent avec les erreurs. lorsque je veux utiliser lfm au niveau des routes on me dit qu’il n’est pas defini voila l’erreur qu’on affiche: Undefined type ‘Unisharp\LaravelFilemanager\Lfm’.intelephense(1009).
    pour NESTEDSET on m’affiche cette erreur :Undefined type ‘Kalnoy\Nestedset\src\NodeTrait’.intelephense(1009) , quant je vais appelle a lui dans le modele comment ce qui fait que meme le table comments n’a aps été rempli.
    besoin d’aide svp merci d’avance

  • fabBlab

    Hello,

    Pas de soucis pour faire fonctionner la page d’accueil, mais par contre quelques trucs concernant FileManager et sa page démo.

    Lorsque je téléverse une image elle apparait en fait dans le répertoire files et non photos.
    Normal car par défaut les fichiers de configuration (lfm.php) indique :
    ‘folder_categories’ => [
    ‘file’ => [
    ‘folder_name’ => ‘files’,
    ‘startup_view’ => ‘list’,
    ‘max_size’ => 50000, // size in KB
    ‘valid_mime’ => [
    ‘image/jpeg’,
    ‘image/pjpeg’,
    ‘image/png’,
    ‘image/gif’,
    ‘image/svg+xml’,
    ‘application/pdf’,
    ‘text/plain’,
    ],
    ],
    ‘image’ => [
    ‘folder_name’ => ‘photos’,
    ‘startup_view’ => ‘grid’,
    ‘max_size’ => 50000, // size in KB
    ‘valid_mime’ => [
    ‘image/jpeg’,
    ‘image/pjpeg’,
    ‘image/png’,
    ‘image/gif’,
    ‘image/svg+xml’,
    ],
    ],
    ],
    Les images sont d’abord considérées comme des fichiers et vont donc se placer dans « files ». J’ai supprimé les « mimes » des images dans la première liste et cela fonctionne comme indiqué.

    Par ailleurs, lorsque je sélectionne un fichier à téléverser, il est automatiquement envoyé sans bouton de confirmation, le « thumbs » n’est pas créé et l’affichage des listes de fichiers déjà présents n’est pas rafraichie.

    Cela est peut-être lié à la démo de FileManager qui serait à actualiser. Par exemple le TinyMCE utilisé est indiqué comme obsolète.

    Par ailleurs, je comprends l’idée de créer un répertoire par « user », mais cela sera à prendre en compte si on prévoit par la suite la possibilité de changer l’auteur d’un article. Il faudra déplacer l’image.

    Sinon concernant le layout, on y trouve : {{ config(‘app.name’) }}
    Je suppose que cela sera revu par la suite pour l’adapter au nom de l’article, etc.

    En tout cas, merci pour ce cas concret que je trouve déjà très instructif.

    • fabBlab

      Je reviens sur ce sujet car ayant avancé dans ce TP, je retrouve le problème des thumbs non créés suite à l’envoi via le formulaire de création/édition des articles.

      En fait, dans la fenêtre me permettant de sélectionner une image à téléverser, il n’y a aucun bouton ou autre, me permettant de valider l’image prévisualisée. Je vois seulement un encadré rouge au niveau de l’image indiquant : [object Object] (message d’erreur très parlant :))

      Dans la console du navigateur, je vois une erreur 500 :

      POST http://127.0.0.1:8000/laravel-filemanager/upload
      État 500
      Internal Server Error
      Version HTTP/1.1
      Transfert 17,64 Ko (taille 16,56 Ko)
      Politique de référent strict-origin-when-cross-origin

      Le bug vient peut-être donc du serveur local. C’est pourtant celui généré par « php artisan serve », qui fonctionne très bien pour le reste.
      Et ce qui est étonnant, ce qu’après avoir fermé la fenêtre de téléversement, sans n’avoir pu rien validé, l’image a tout de même été téléversée, le dossier thumbs créé, mais pas la miniature.

      Je peux aussi déplacer des images déjà présentes, mais rencontre des bugs pour les rogner ou redimensionner…

      Bref, je pense que c’est un problème venant de FileManager. En tout cas, rien à voir avec le code du blog.

  • khadidja

    Bonjour, j’ai créer un projet , le filemanager a fonctionner très bien, mais quand j’ai creer un autre pojet et suivit les étapes j’ai cette erreur:
    Unable to prepare route [laravel-filemanager] for serialization. Another route has already been assigned name [unisharp.lfm.show]

  • abinkanrin

    Salut. Merci pour ces efforts fournis. J’ai une erreur.
    Illuminate\Contracts\Container\BindingResolutionException
    Target class [App\Http\Controllers\App\Http\Controllers\Front\PostController] does not exist.
    Merci de m’aider

  • bensa

    Salut chef,

    stp par rapport au CSS, en cliquant sur un lien ou bien un onglet, ca prend beaucoup de temps avec un écran noir pour afficher la page de déstination.

    y a t-il un moyen pour contourner cette durée d’attente?
    merci

  • pascalain004

    Bonjour et merçi pour ce nouveau tuto.
    j’ai une erreur que les autres avant moi n’ont pas eu

    Error
    Call to undefined function getImage() (View: C:\laragon\www\monblog\resources\views\components\front\brick.blade.php)

  • oufana

    Merci pour les tutos;
    J’ai cette erreur:Class App\Repositories\PostRepository located in C:/laragon/www/monblog.ext/app\Repositoires\PostRepository.php does not comply with psr-4 autoloading standard. Skipping.
    NB:j’utilise Laragon.
    Merci de votre aide.

  • braice

    Bonjour,

    D’abord bravo pour tes tutos, tu expliques très bien les différentes étapes. Je bloque sur les routes, j’ai refait l’installation de breeze et bien suivi les étapes pour les routes pourtant je n’arrive pas à accéder à la route: monblog.ext/login. Une idée ?

  • Michel

    Bonjour,

    A la fin de ce tuto, j’en suis là. Page connexion > connexion avec le user qui a l’id2 > dashbord et un message > vous étes connecté. Page Laravel…
    J’e pense avoir bien suivi toutes les étapes pourtant…Où est mon erreur (en supposant qu’il n’y en ai qu’une)

    Merci pour votre aide.

  • fifi

    Bonjour bestmomo;
    je n’ai pas pu installer package:

    composer require unisharp/laravel-filemanager
    Using version ^2.2 for unisharp/laravel-filemanager
    ./composer.json has been updated
    Running composer update unisharp/laravel-filemanager
    Loading composer repositories with package information
    Updating dependencies
    Your requirements could not be resolved to an installable set of packages.

    Problem 1
    – Root composer.json requires unisharp/laravel-filemanager ^2.2 -> satisfiable by unisharp/laravel-filemanager[v2.2.0].
    – unisharp/laravel-filemanager v2.2.0 requires ext-exif * -> it is missing from your system. Install or enable PHP’s exif extension.

    To enable extensions, verify that they are enabled in your .ini files:
    – C:\laragon\bin\php\php-7.4.13-Win32-vc15-x64\php.ini
    You can also run `php –ini` inside terminal to see which files are used by PHP in CLI mode.

    Installation failed, reverting ./composer.json and ./composer.lock to their original content.

  • webwatson

    Bonjour à vous!
    Moi je ne peux plus continuer la formation car je n’arrive pas à télécharge le template.
    Le fichier zip du projet, je l’ai téléchargé monblog1 mais il ne contient pas de dossier dans storage ni les fichier style et js de calvin.

  • oksam

    Bonjour Best, je suis au niveau des Médias. j’ai installé les packages, les configuartions et le lien. seulement j’arrive pas à me connecter avec l’url monblog.ext/login et dans mon dossier public/storage il n’y a pas de fichiers file et de share. j’ai réinstaller comme tu as suggéré mais rien.

    • bestmomo

      Bonjour,

      Pour la partie login il faut bien installer Breeze comme je l’ai indiqué dans le premier article. d’autre part j’ai mis l’estension « ext » comme un exemple, tout dépend de la plateforme de développement utilisée.

      Pour le dossier public/storage il faut créer le lien symbolique comme je l’ai indiqué dans l’article. D’autre part il faut aller chercher les dossiers avec les images dans le fichier ZIP à télécharger.

Laisser un commentaire