Bonjour,

Voila je rencontre un petit problème avec mon code.

je voudrais afficher mes données avec ma vue.php, elle n'arrive pas a trouvé une variable je vous montre mon architecture, je débute en architecture MVC si vous avez des astuces n'hésiter pas, merci de votre aide

index.php qui joue le rôle de routeur

<?php
require('controlWord.php');
require('vueWord.php');

if (isset($_GET['action'])) {
    if ($_GET['action'] == 'Controlword') {
        new Controlword();
    }
    elseif ($_GET['action'] == 'Controlword') {
        if (isset($_GET['id']) && $_GET['id'] > 0) {
            new Controlword();
        }
        else {
            echo 'pas de mot';
        }
    }
}
else {
    new Controlword();
}

Mon modèle qui fournit l'accès et les données

<?php
class Modelword
{
    // Renvoie la liste des billets du blog
  public function getWord() {
    $bdd = $this->getBdd();
    $words = $bdd->query('SELECT id, mot FROM hello');
    return $words;
  }

  private function getBdd() {
    $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
    return $bdd;
  }
}

Ma vue qui affiche le tout je sais que c'est elle l'erreur mais est ce que je dois en faire une classe et créer un gabarit ou je la laisse comme ça

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Mes mots</title>
        <link href="style.css" rel="stylesheet" /> 
    </head>

    <body>

        <?php
        while ($data = $word->fetch())
        {
        ?>    

                <p><?= htmlspecialchars($data['mot']) ?></p>
        <?php
        }
        $word->closeCursor();
        ?>

    </body>
</html>

Mon controller qui controle et fournis à la vue et là je sais pas trop comment m'y prendre

<?php

require('modelWord.php');
require('vueWord.php');

class Controlword
{
    private $words;

    public function __construct() {
      $this->words = new Modelword();
    }

    public function getWord()
  {
    $word = $this->words->getBillets();
  }
}

Je cherche des piste pour afficher mes données

Ce que j'obtiens une variable indéfinis sur la vue

26 réponses


Lartak
Réponse acceptée

Il te faut stocker dans une variable le retour de la méthode words dans ton fichier index, car quand tu retournes la variable, c'est ce qu'elle contient que tu retourne, donc par exemple :

$word = $controlWord->word();
quenti77
Réponse acceptée

Je te conseil vivement de revoir un peu le code et surtout l'ordre dans lequel il s'exécute. La tu affiche la vue vueWord.php alors que déjà c'est peut-être pas elle que tu voudra et aussi car les variables ne sont transmis au fichier require que si elles se trouvent avant le require.

Il faudrait donc inverser mais le mieux est de revoir un peu l'architecture et voici ce que j'ai fais de mon côté :

Déjà mon architecture c'est :

  • Un dossier public avec le fichier index.php
  • Un dossier src avec dedans 3 sous-dossiers (mais il peut y en avoir plus) qui sont Controllers, Models et Views

Dans src/Models/Connection.php on a le code :

<?php

namespace Tuto\Models;

use PDO;
use PDOStatement;

/**
 * @package Tuto\Models
 * Class Connection
 */
class Connection extends PDO
{
    /**
     * Connection constructor
     * 
     * @param string $dsn
     * @param string $user
     * @param string $pass
     * @param array $options
     */
    public function __construct(string $dsn, string $user, string $pass, array $options = [])
    {
        parent::__construct($dsn, $user, $pass, $options);

        parent::setAttribute(parent::ATTR_DEFAULT_FETCH_MODE, parent::FETCH_ASSOC);
        parent::setAttribute(parent::ATTR_ERRMODE, parent::ERRMODE_EXCEPTION);
        parent::setAttribute(parent::ATTR_EMULATE_PREPARES, false);
    }

    /**
     * Cette méthode peut-être plus poussé (demande moi si tu veux voir)
     * 
     * @param string $statement
     * @param array $params
     * @return PDOStatement
     */
    public function request(string $statement, array $params = []): PDOStatement
    {
        $request = $this->prepare($statement);
        $request->execute($params);

        return $request;
    }
}

Dans src/Models/WordModel.php on a le code :

<?php

namespace Tuto\Models;

use PDOStatement;

/**
 * @package Tuto\Models
 * Class WordModel
 */
class WordModel
{
    /** @var Connection $connection */
    private $connection;

    /**
     * WordModel Constructor
     * 
     * @param Connection $connection
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * @return PDOStatement
     */
    public function all(): PDOStatement
    {
        // J'aurai surement boucler pour mettre dans des entitées
        // Et cette classe serait un repository et non un model
        // Mais pour le moment on fait au plus simple.
        return $this->connection->request('SELECT id, mot FROM hello');
    }
}

Dans src/Controllers/WordController.php on a le code :

<?php

namespace Tuto\Controllers;

use Tuto\Models\WordModel;

/**
 * @package Tuto\Controllers
 * Class WordController
 */
class WordController
{
    /** @var WordModel $wordModel */
    private $wordModel;

    /**
     * WordController Constructor
     * 
     * @param WordModel $wordModel
     */
    public function __construct(WordModel $wordModel)
    {
        $this->wordModel = $wordModel;
    }

    /**
     * @return array
     */
    public function index(): array
    {
        $words = $this->wordModel->all();

        return [
            'views' => __DIR__ . '/../Views/WordView.php',
            'words' => $words
        ];
    }
}

La vue qui se trouve dans le fichier src/Views/WordView.php à le code :

<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Première page</title>
</head>
<body>

    <!-- faire un système de layout -->
    <h1>Liste des mots :</h1>

    <?php foreach ($words as $word): ?>
    <div>
        <?= $word['id'] ?> : <strong><?= $word['mot'] ?></strong>
    </div>
    <?php endforeach; ?>

</body>
</html>

Enfin le fichier public/index.php contient :

<?php

// Autoloader
spl_autoload_register(function (string $className) {
    if (strpos($className, 'Tuto\\') === 0) {
        $className = str_replace('Tuto\\', '', $className);
        $className = str_replace('\\', '/', $className);

        require __DIR__ . "/../src/{$className}.php";
    }
});

// Je vais faire les checks de paramètre mais
// personnelement j appel pas vraiment ça un routing ^^
// et ça devra être fait ailleurs

use Tuto\Models\Connection;
use Tuto\Models\WordModel;
use Tuto\Controllers\WordController;

$connection = new Connection('mysql:host=localhost;dbname=test;charset=utf8', 'root', 'root');

$controller = $_GET['controller'] ?? 'word';
$action = $_GET['action'] ?? 'index';

$controllers = [
    'word' => [
        'instance' => function () use($connection) {
            $wordModel = new WordModel($connection);
            return new WordController($wordModel);
        },
        'actions' => [
            'index'
        ]
    ]
];

$currentController = $controllers[$controller] ?? $controllers['word'];
$currentInstance = $currentController['instance']();

// Pour éviter tous soucis avec extract sur de potentiel variable ecrasé
function render(array $data = [])
{
    $views = $data['views'] ?? '';
    if (file_exists($views)) {
        unset($data['views']);
        extract($data);

        require $views;
    }
}

if (in_array($action, $currentController['actions']) && method_exists($currentInstance, $action)) {
    $data = $currentInstance->$action();
    render($data);
} else {
    // ERROR;
}

J'espère que ça peut te permettre de mieux comprendre (même si la j'ai vraiment simplifier par rapport à ce que l'on peut faire pour avoir un bon code.

quenti77
Réponse acceptée

Alors attention, ce que tu appel manager, j'ai appelé repository mais c'est grosso modo la même chose. La seul vrai entité que tu as pour le moment c'est "Word" (sans le s vu que c'est pour une seul ligne)

En gros dans les repositories (ou les managers comme tu veux ^^) tu va pouvoir transformer le tableau de PDO en Entité.

Si je reprends le code de mon WordModel.php que je vais renommé en WordsRepository (ou WordsManager si tu préfère). J'en profite pour renommer le dossier "Models" en "Repositories" et les namespaces qui vont avec.

Ensuite je créé un dossier "Entities" dans src et un fichier "Word.php" (qui est donc notre entité. On aurait cette classe là :

<?php

namespace Tuto\Entities;

/**
 * @package Tuto\Entities;
 * Class Word
 */
class Word
{
    // Pour simplifier le code et les explications je
    // mets l'hydratation directement dans cette classe
    // mais on pourrait très bien y mettre un trait par
    // exemple HydratableTrait qui aurait le code de la
    // méthode hydrate que l'on va avoir ici

    /** @var int $id */
    private $id;

    /** @var string $mot */
    private $mot;

    /**
     * Word Constructor
     * @param array $cols
     */
    public function __construct(array $cols = [])
    {
        $this->hydrate($cols);
    }

    /**
     * @param array $cols
     */
    public function hydrate(array $cols = [])
    {
        foreach ($cols as $name => $value) {
            // Transforme du snake_case en CamelCase et prefix avec set
            // exemple : 'create_at' devient => 'setCreatedAt'
            $methodName = 'set' . str_replace('_', '', ucwords($name, '_'));

            if (method_exists($this, $methodName)) {
                // Petite particularité ici. On peut appeler dynamiquement
                // une méthode, propriété et même une variable avec $$methodName (par exemple)
                // Donc la si je reprends l'exemple du created_at on va appeler la méthode
                // setCreatedAt sur l'objet courant ($this)
                $this->$methodName($value);
            }
        }
    }

    // Je simplifie les get/set même si c'est pas psr-1
    public function getId(): int { return $this->id; }
    public function getMot(): string { return $this->mot; }

    public function setId(int $value): void { $this->id = $value; }
    public function setMot(string $value): void { $this->mot = $value; }

    // Juste pour comprendre pourquoi le setter est intéressant ici je vais continue avec
    // l'exemple du created_at même si je ne l'ai pas dans ma table
    public function setCreatedAt($value): void
    {
        if (is_string($value)) {
            $value = DateTime::createFromFormat('Y-m-d H:i:s', $value);
        }

        $this->createdAt = $value;
    }
}

Quelque particularité de code que j'ai essayé de commentrer au mieux. Pour la suite on va modifier le fichier WordsRepository pour ressembler à ceci :

<?php

namespace Tuto\Repositories;

use PDOStatement;
use Tuto\Entities\Word;

/**
 * @package Tuto\Repositories
 * Class WordsRepository
 */
class WordsRepository
{
    /** @var Connection $connection */
    private $connection;

    /**
     * WordModel Constructor
     * 
     * @param Connection $connection
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * @return Word[]
     */
    public function all(): array
    {
        $request = $this->connection->request('SELECT id, mot FROM hello');

        $words = [];
        foreach ($request as $word) {
            $words[] = new Word($word);
        }

        return $words;
    }
}

Ne pas oublier de rename dans tous les fichiers les noms comme Tuto\Models\WordModel en Tuto\Repositories\WordsRepository et tout ce qui va avec.

Dans la vue il faut juste changer la boucle foreach qui devient :

<?php foreach ($words as $word): ?>
  <div>
    <?= $word->getId() ?> : <strong><?= $word->getMot() ?></strong>
  </div>
<?php endforeach; ?>

Dans le controller j'ai renommé les WordModel en WordsRepository

Bonjour.
Tu devrais commencer par expliquer pourquoi tes conditions dans le if elseif sont les mêmes, soit $_GET['action'] == 'Controlword' dans le fichier index.php (soit le router d'après ce que tu dis).
Ensuite, dans ce même fichier, tu ne fais qu'initialiser une classe, si tu n'utilises pas une méthode de la classe et que tu ne stockes pas son retour dans une variable, le reste de ton code ne pourra pas avoir accès à cette variable et à ce qu'elle contient, tu n'aura par conséquent rien de plus que l'instance de la classe, que tu dois elle-même stocker dans une variable.

Pour les conditions je les ai vidé comme ceci :

require('controlWord.php');
require('vueWord.php');

if (isset($_GET['action'])) {
}
else {
    word();
}

et aussi c'est le else qui m'intéresse car c'est lui qui affiche les donnés, pour l'instance je n'ai pas de fonction où j'ai stocké une variable. Car je ne l'ai pas encore creer

Je t'ai dit qu'il te fallait stocker l'instance de la classe, pour pouvoir utiliser une méthode de la classe dessus, de plus que tu as bien une méthode dans la classe : getWord, par contre, tu y fait appel à une méthode de la classe Modelword qui n'existe pas, par contre tu y as la méthode getWord, c'est donc cette méthode là que tu dois utiliser sur la proprété words.
Et puis, dans la méthode getWord de la classe Controlword, si tu ne retournes pas la variable $words, tu ne pourras pas y accéder depuis l'extérieur de la méthode, il te faudra d'ailleurs stocker son retour dans une variable.

Ok pour la classe Controlworld j'ai fait ceci

<?php

require('modelWord.php');
require('vue.php');
class Controlword
{
    private $words;

    public function __construct() {
      $this->words = getWord();

    }
    public function word()
  {
    $word = $this->words->getWord();
    return $word;
  }
}

Et pour l'index ceci :

<?php
require('controlWord.php');
require('vueWord.php');

$controlWord= new Controlword();
if (isset($_GET['action'])) {
}
else {
   $controlWord->word();
}

Merci je vais regarder ça attentivement merci pour ton aide

Bonjour et merci encore pour votre aide et quenti77 j'aimerais en apprendre plus sur la fonction request et aussi en savoir plus sur le repository si possible et encore merci pour ton aide

Si tu veux le détails de la méthode request je te passe déjà ce lien (qui est le fichier complet de la connexion) :
https://github.com/quenti77/old-projects/blob/master/phq/src/Database/PDO/Connection.php

En gros le but de ce fichier et de la méthode est de ne pas à avoir à s'embêter trop avec les valeurs à transmettre. Exemple je fournis un objet de type DateTime, il appel automatiquement la méthode format pour la transformer en chaine.

Tu peux voir tout le repo du projet (même si je le maintient plus à jour maintenent)

Pour les repository, dans mon cas c'est une classe qui va gérer les requêtes et permettre de récupérer des données. En gros c'est ici que tu va y faire tes requêtes grâce à la connexion et à la méthode request. Tu pourra plus tard passer par un DQL pour simplifier l'écriture. Les repositories si tu fais des requêtes de type SELECT vont aussi faire la transformation des résultats en entité (en tout cas au début ^^)

PS: Tu peux y voir dans le dossier app une architecture un peu différente du MVC, déjà c'est plus de l'ADR et comme j'ai mis des sortes de modules c'est hiérarchisé donc HADR (sachant que le HMVC existe aussi ^^)

Après il y a des choses que je pourrais améliorer encore surtout avec les outils php 7 que j'ai pas encore trop utilisé ou des choses que j'ai apprisent depuis comme certains patern que l'on voit dans le DDD.

Ok merci c'est sympa

Bonsoir quanti77 tu peux m'en dire plus cette fonction avec les entités

public function all(): PDOStatement
    {
        // J'aurai surement boucler pour mettre dans des entitées
        // Et cette classe serait un repository et non un model
        // Mais pour le moment on fait au plus simple.
        return $this->connection->request('SELECT id, mot FROM hello');
    }

car en ce moment j'essaie de mettre des entités j'en ai fait deux une word et une wordmanager
la Word

<?php
namespace Hello\Models;

class Words{
  private $_id;
  private $_mot;

  public function hydrate(array $donnees)
  {
    foreach ($donnees as $key => $value)
    {
      // On récupère le nom du setter correspondant à l'attribut.
      $method = 'set'.ucfirst($key);

      // Si le setter correspondant existe.
      if (method_exists($this, $method))
      {
        // On appelle le setter.
        $this->$method($value);
      }
    }
  }

  public function __construct(array $donnees)
  {
      $this->hydrate($donnees);
  }

  public function id() { return $this->_id; }
  public function mot() { return $this->_nom; }

  public function setId($id)
  {
    $this->_id = (int) $id;
  }

  public function setMot($mot)
  {

    if (is_string($mot))
    {
      $this->_mot = $mot;
    }
  }

}

La Managerword

<?php
namespace Hello\Models;

class Managerword
{

  private $_db; // Instance de PDO

  public function __construct($db)
  {
    $this->setDb($db);
  }

  public function getList()
  {
    $persos = [];

    $q = $this->_db->query('SELECT id, mot FROM hello');

    while ($donnees = $q->fetch(PDO::FETCH_ASSOC))
    {
      $persos[] = new Words($donnees);
    }

    return $persos;
  }

  public function setDb(PDO $db)
  {
    $this->_db = $db;
  }
}

j'essaie de les mettre dans la fonction all() mais je vois pas trop comment m'y prendre mais je continue de chercher

ok merci pour cette leçon

Bonsoir Quenti77 Comment vas tu ? Merci encore pour la leçon sur la repository ça m'a été super utile mais j'ai une petite question j'essaye de faire un lien pour afficher une ligne selectionner avec son ID et j'éssaye de passer par GET mais sans grand succès mais je continue chercher mon lien dans la vue ressemble à ça

<a href="index.php?id=<?= $billet->getId() ?>">

je sais que ça équivaut à ça #

donc je me demandais comment faire pour obtenir un lien qui m'affchiche une ligne sélectionner avec son id et aussi savoir si ils sont déjà dans un GET ou s'il faut en creer un avec try puis if sans oublier le catch pour les erreurs et tout ça dans index.php

Bonjour quenti77
J'ai une question je me demandais si je pouvais avec les variables controller

$controller = $_GET['controller'] ?? 'word';
$action = $_GET['action'] ?? 'indexWord';

je pourrrais faire ça ou gerer plus de fonctions dans controllers :

$controller = $_GET['controller'] ?? 'word'&&$_GET['controller'] ?? 'number';
$action = $_GET['action'] ?? 'index'&&'indexNumber';

ou il y a un autre moyen pour que le controller controle plus de $_GET

dans index je parle car c'est pour la variable controlers

$controllers = [
    'word' => [
        'instance' => function () use($connection) {
            $wordModel = new WordModel($connection);
            return new WordController($wordModel);
        },
        'actions' => [
            'index'
        ]
    ],'number'=>function () use($connection) {
            $numberModel = new NumberModel($connection);
            return new NumberController($numberModel);
        },
        'actions' => [
            'index'
        ]
];

Alors je vais dire oui tu peux y mettre plus d'actions dans ton controller en ajoutant simplement la/les méthodes dans le controller concerné et dans le tableau 'actions' du controller en question. Après c'est une première version basique d'un système de routing et le mieux serait d'avoir un vrai truc (que tu fais toi ou tu utilise une lib) et avoir un fichier de route comme ceci :

$router->get('/words', 'WordController@index');
$router->get('/words/:id', 'WordController@details');

$router->post('/words/:id', 'WordController@update');
// etc ...

Ensuite pour gérer les injections de dépendances le mieux c'est d'avoir un DIC (que tu fais toi-même ou avec une lib) ce qui permet de pas à avoir à ce soucier des dépendances nécessaire comme le WordModel que l'on demande au constructeur du controller.

Faire un router et un dic soi-même pour apprendre c'est vraiment bien mais par contre c'est assez complexe surtout si tu pousse un peu le système. (Exemple j'avais avant d'utiliser des libs, fait un router qui gère des middlewares et des groupes de routes pour s'implifier et le dic juste de base était déjà un assez gros morceaux. Maintenant je préfère utiliser des libs comme zend-fast-route-router et php-di)

Ok merci cool mais pour le moment je veux juste ajouter des actions pour accomplir plus de fonction et malheureusement je peux pas me servir de framework pas contre je les gardes en mémoire ça pourrait m'être utile

la partie faire le dic et le router soi-même ça m'interesse tu connais des tutos qui t'explique comment faire ? Sinon je me débrouille en tout cas merci de partager avec moi tes connaissances ça m'aide grave et j'espère ne pas trop te déranger

Pour un premier exemple de router et dic tu as :

ça permet de poser les bases (même s'il reste quand même à les intégrer dans une app complète).

ok merci, pour le systeme de routing basique tu t'y prends comment pour ajouter les actions et méthodes

et faire en sorte qu'elle soit séparer de la première actions

Il faudrait que je t'explique plus facilement qu'a l'écrit car la ça devient compliquer d'expliquer sans montrer des exemples. Et par message différé dans le forum ça n'aide pas. La j'ai bientôt finis un projet de mvc sans aucune lib externe (mais j'utilise composer pour l'autoloader car faut pas déconné non plus ^^). Il contient le strict minimum pour du mvc avec :

  • DIC (pour ne pas s'embêter avec l'injection de dépendanc)
  • Router (pour gérer les URL)
  • Repositories (Pour gérer les requêtes SQL)
  • Request/Response (Car dans le web le MVC à besoin de recevoir une requête et de retourner une réponse)

Ok dac pour l'explication si tu veux je te donne un mail pototron@gmx.fr et on verra pour l'explication

Désolé, j'avais finis le petit projet mais j'ai complètement zappé de l'envoyer sur github.
Voici le lien de l'exemple de MVC : https://github.com/quenti77/mvc-php-exemple

Pas grave c'est déjà sympas que tu m'aides, merci, c'est sympas