Bonjour.

Dans le chapitre 19 du Tutorial, j'ai une erreur Class not found alors que le chemin de ma classe est correct

Fatal error: Uncaught Error: Class "C:\xampp\htdocs\www\POO\Blog\app\Table\ArticlesTable" not found in C:\xampp\htdocs\www\POO\Blog\app\App.php:29 Stack trace: #0 C:\xampp\htdocs\www\POO\Blog\public\index.php(9): App->getTable('Articles') #1 {main} thrown in C:\xampp\htdocs\www\POO\Blog\app\App.php on line 29

Ci-dessous le code de ma classe App.php

"<?php

use \Core\Config;
use \Core\Database\MysqlDatabase;

class App {

    public $title = 'Blog';
    private $db_instance;
    private static $_instance;

    public static function getInstance() {
        if(self::$_instance === null) {
            self::$_instance = new App(); // mais pas de constructeur !!
        }
        return self::$_instance; //toujours NULL
    }

    public static function load() {
        session_start();
        require ROOT.'/app/Autoload.php';
        App\Autoload::register();
        require ROOT.'/core/Autoload.php';
        Core\Autoload::register();
    }

    public function getTable($name) {
        $class_name = ROOT.'\\app\\Table\\'.ucfirst($name).'Table';
        return new $class_name($this->getDb());
    }

    public function getDb() {
        $config = Config::getInstance(ROOT.'/config/config.php');
        if(is_null($this->db_instance)) {
            $this->db_instance = new MysqlDatabase($config->get('db_name'), $config->get('db_host'), $config->get('db_user'), $config->get('db_password'));
        }
        return $this->db_instance;
    }
}

?>

et celui de ma page index.php :

<?php

define('ROOT', dirname(__DIR__));
require ROOT.'/app/App.php';

App::load();

$app = App::getInstance();
$articles = $app->getTable('Articles');

J'ai vu plein de sujets là-dessus, mais aucun de semble répondre à ma question.
Quelqu'un aurait une idée ? Merci de votre aide.

Ce que je veux

Décrivez ce que vous cherchez à obtenir.

Ce que j'obtiens

Décrivez vos éventuelles erreurs ou ce que vous obtenez à la place de ce que vous attendez :(

28 réponses


popotte
Réponse acceptée

ahah avec plaisir ;)
oui cnest vrai qu´il apporte pas mal de choses pour apprendre ^^
et encore ce n´est rien, tu auras de quoi apprendre sur des meilleures pratiques en regardant les conferences, le SOLID, le clean code etc... (bon par contre les truc poussés y´a que des resources en anglais :x)

alors sur une de mes reponses doit y´avoir un bouton pour dire que le probleme a ete reglé, tu cliques et ca va marquer le sujet comme "résolu" ;)

popotte
Réponse acceptée

Avec plaisir ;)
Oui c'est sur on apprends pas mal de choses, et encore c'est juste la base, tu as beaucoup de choses à apprendre, par contre les truc plus poussés ne sont dipo qu'en anglais (les conferences)

il y'a une icone "V" a coté d'une de mes réponses normalement (je ne suis pas l'auteur donc je vois rien mais en principe y'a ça)
Tu cliques dessus ca marque comme "résolu" ;)

(j'ai déjà répondu mais on dirait que mon mesage a été supprimé :/)

Alors peut etre que si au lieux de faire un chemin direct avec ROOT tu utilisait le namespace ca devrait le faire (ensuite ton autoload.php s'occupera lui même de définir le path par rapport au namespace) ^^

    public function getTable($name) {
        $class_name = 'App\Table\' . ucfirst($name) . 'Table'; // En partant du principe que le namespace soit App\Table\ArticlesTable
        return new $class_name($this->getDb());
    }

Parce que la dans ta fonction t'es en train de faire le boulot de l'autoloader :p

catyoh
Auteur

Cela ne fonctionne pas. L'autoloader du namespace App ne trouve pas le fichier Table.php qui se trouve dans le répertoire core...

Erreur : C:\xampp\htdocs\www\POO\Blog/app/Core\Table\Table.php): Failed to open stream No such file or directory in C:\xampp\htdocs\www\POO\Blog\app\Autoload.php on line 27

Le fichier se trouve bien dans C:\xampp\htdocs\www\POO\Blog/app/Core\Table\Table.php? La il ne cherche pas dans le repertoire core mais dans le repertoire core/table

Mmmh bon maintenant que ça passe par le namespace c'est l'autoloader qui joue, et cette fois l'erreur est venu a partir de l'autoloader :/

Tu peux envoyer le contenu de Autoload.php?

catyoh
Auteur

Il y a deux Autoload.php, identiques sauf leur namespace respectif. L'un est dans le répertoire app et le namespace App, l'autre dans le répertoire core et le namespace Core. Ci-dessous l'Autoload.php du répertoire app :

<?php
namespace App;

class Autoload {

    /**
     * Fonction statique autoload : récupère le chemin absolu des fichiers à inclure
     * @param string $class_name : nom de la classe
     */
    static function autoload($class_name) {
        if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
            $class_name = str_replace(__NAMESPACE__.'\\','', $class_name);
            require __DIR__.'\\'.$class_name.'.php';
        }
        else {
            require __DIR__.'\\'.$class_name.'.php';
        }
    }

    /**
     * Fonction statique register : inclue automatiquement les fichiers à inclure
     */
    static function register() {
        spl_autoload_register(array(__CLASS__, 'autoload'));
    }   
}
?>
catyoh
Auteur

En fait, en débuggant App\Autoload.php, je m'aperçois qu'il ne reconnaît pas le namespace Core\Table :

static function autoload($class_name) {
        if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
            $class_name = str_replace(__NAMESPACE__.'\\','', $class_name);
            var_dump(__DIR__.'\\'.$class_name); // var_dump 1
            require __DIR__.'\\'.$class_name.'.php';
        }
        else {
            var_dump(__DIR__.'\\'.$class_name); // var_dump 2
            require __DIR__.'\\'.$class_name.'.php';
        }
    }

var_dump 1 : string(51) "C:\xampp\htdocs\www\POO\Blog\app\Table\ArticleTable" OK
var_dump 2 : string(49) "C:\xampp\htdocs\www\POO\Blog\app\Core\Table\Table" pas OK. Le chemin de Table.php devrait être C:\xampp\htdocs\www\POO\Blog\core\Table\Table.php.

Je sèche là dessus... et désespère d'avancer. J'ai refait trois fois le chapitre... et je tourne en rond. Votre aide serait la bienvenue...

Bizarre pourtant ton autoload semble correct :/

Alors perso je remplace les backslash par des slash, c'est l'autoload que je fais habituellement et il fonctionne alors peut être la :/

static function autoload($class_name){
    $class_name = str_replace(__NAMESPACE__ . '\\', '', $class_name);
    $class_name = str_replace('\\','/',$class_name); 
    if(file_exists(__DIR__ . '/' . $class_name . '.php')){
        require __DIR__ . '/' . $class_name . '.php'; 
    }
}

Ensuite autre possibilité, tu à regardé la trace de l'erreur, si tu remontes jusqu'a l'appel du namespace qui requier l'autoload tu n'a pas ajouté un "Table" en trop dans le namespace?

Et pour cette fonction:

public function getTable($name) {
        $class_name = ROOT.'\\app\\Table\\'.ucfirst($name).'Table';
        return new $class_name($this->getDb());
    }

Tu as bien une class Table? Si oui ce n'est pas ça le problème, si tu n'a pas de class Table alors à un moment ta valeur $name est null alors qu'il devrait avoir une valeur

catyoh
Auteur

Merci Popotte pour ton aide.
Pour les slashes c'est OK sous Windows. Je suppose que tu es sous Linux ?
Ce que montrent les var_dump que j'ai envoyés ci-dessus, c'est que l'Autoload du namespace App ne reconnaît pas Core\Table comme un namespace. Du coup, ça passe dans la condition else qui ne supprime pas le namespace Core\Table et le DIR qu'il trouve est le répertoire app et pas le répertoire core.
Donc, il ne trouve pas la classe Table qui est dans le répertoire core\Table et dans le namespace Core\Table.
Et je vois pas comment l'éduquer, ce fichu Autoload. J'ai essayé de supprimer le else, résultat, ça tourne en boucle infinie.... :))

De fait, pour charger la classe Articles, il lui faut la classe Table qui est parent de Articles...

De rien ;)

Je suis sur Windows mais je dev sur une VM Linux oui ^^' (en général les hebergeurs web sont sous linux du coup je prefère dev en linux pour éviter les surprises :p)

Mmmmh alors si ça fonctionne quand c'est App et pas quand c'est Core, c'est peut ëtre la faute du App autoload

En gros t'a fonction load d'abord le autoload app, puis ensuite le core, quand tu lances une class Core, il va d'abord essayer avec l'autoload de app, et cet autoload doit surement planter avant que le script essayes avec core
Dans ta fonction load(), essayes d'inverser les deux autoloads pour voir ce que ça donne

Okay alors ce n'est pas toi qui appelles Table, mais c'est ArticleTable qui appelle Table pour son extends, peut etre une erreur par la, fait voir ArticleTable :o

catyoh
Auteur

Quand on inverse les deux autoloads dans la fonction load() d'App, on obtient :

string(56) "C:\xampp\htdocs\www\POO\Blog\core\App\Table\ArticleTable"
Warning: require(C:\xampp\htdocs\www\POO\Blog\core\App\Table\ArticleTable.php): Failed to open stream: No such file or directory in C:\xampp\htdocs\www\POO\Blog\core\Autoload.php on line 16

Donc, l'autoload du namespace Core ne reconnaît pas App\Table comme un namespace (seul le var_dump 2, celui contenu dans le else, renvoie un chemin qui devrait être app\Table\ArticleTable).

Donc l'ordre était bien le bon. Ce qui est logique, puisque c'est ArticleTable qui cherche Table. L'autoload du namespace App trouve bien la classe ArticleTable, mais comme il ne trouve pas la classe Table, ça plante.

Ci-dessous le début de la classe ArticleTable :

<?php

namespace App\Table;

use Core\Table\Table;

class ArticleTable extends Table {

    protected $table = 'articles';

    /**
     * Récupère les derniers articles
     * @return array
     */
    public function last() {
        return $this->query("
            SELECT articles.id, articles.titre, articles.content, articles.date, categories.titre as categorie
            FROM articles
            LEFT JOIN categories ON categorie_id = categories.id
            ORDER BY articles.date DESC
            ");
    }
    [...]

Ah par contre j'ai mal lu un truc:

En fait, en débuggant App\Autoload.php, je m'aperçois qu'il ne reconnaît pas le namespace Core\Table :

static function autoload($class_name) {
        if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
            $class_name = str_replace(__NAMESPACE__.'\\','', $class_name);
            var_dump(__DIR__.'\\'.$class_name); // var_dump 1
            require __DIR__.'\\'.$class_name.'.php';
        }
        else {
            var_dump(__DIR__.'\\'.$class_name); // var_dump 2
            require __DIR__.'\\'.$class_name.'.php';
        }
    }

var_dump 1 : string(51) "C:\xampp\htdocs\www\POO\Blog\app\Table\ArticleTable" OK
var_dump 2 : string(49) "C:\xampp\htdocs\www\POO\Blog\app\Core\Table\Table" pas OK. Le chemin de Table.php devrait être C:\xampp\htdocs\www\POO\Blog\core\Table\Table.php.

Je sèche là dessus... et désespère d'avancer. J'ai refait trois fois le chapitre... et je tourne en rond. Votre aide serait la bienvenue...

Pour le fait que l'autoload App ne reconnaisse pas le namespace Core, c'est normal, ton autoload est dans App/, et tu utilises la fonction DIR qui retourne le fichier courant (qui est "app/")

D'ailleurs je pense à quelque chose, mais c'est inutile d'avoir plusieurs autoloads :/ Il faudrait un seul autoload à la racine du projet, ensuite tes class qui sont dans les dossiers App et Core l'autoload de la racine pourras les trouver (les class App ont bien le App dans leur namespace, et les class Core ont le Core dans leur namespace)

Donc en gros si tu fais un autoload en racine ça donerait ça:

// Pour les classes App
static function autoload($class_name) {
        if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
            $class_name = str_replace(__NAMESPACE__ . '\\', '', $class_name);

            /**
            * Ca donnera RacineDuProjet/App/BlaBla/TaClass.php pour les classes App
            * Ca donnera RacineDuProjet/Core/BlaBla/TaClass.php pour les classes Core
            */
            require __DIR__.'\\'.$class_name.'.php';
        }
        else {
            require __DIR__.'\\'.$class_name.'.php';
        }
    }

Bref l'autoloader est à la base conçu pour être unique et charger à lui seul toutes les classes de toutes les categories

catyoh
Auteur

Je suis d'accord avec toi sur la logique de ta proposition... sauf que j'ai suivi le tuto de Grafikart... qui a deux autoloaders. Et ça fonctionne dans la video. Donc c'est que j'ai loupé quelque chose et je n'arrive pas à trouver quoi.

Merci en tout cas d'avoir pris le temps de chercher... Je vais retenter de refaire une fois encore le refactoring... en repartant du moment où ça fonctionnait.

Par contre je n'arrive pas à trouver la vidéo de grafikart tu pourrais me mettre le lien?
Et aussi elle est de quelle année le tuto? peut être que dans les anciennes versions de PHP c'etait necessaire de passer par plusieurs autoloader mais plu dans les nouvelles version

De rien ;) Bon c'est sur que du point de vue exterieur je ne peux pas debug et je peux seulement emmetre des hypothèses, désolé de ne pas avoir pu être très utile ^^'

catyoh
Auteur

https://grafikart.fr/tutoriels/tp-refactor-572#autoplay
La version date d'il y a trois ans

Okay je vien de voir la vidéo, alors ta class BlablaTable qui extends de Table, il extends de Core\Table ou de \Core\Table? Grafikart est tombé sur la même erreur que toi plus loin dans la vidéo et a expliqué qu'il ne faisait pas attention parce qu'il a l'habitude de travailler sur un IDE

Bref y'a une partie de la vidéo où c'est normal que ton code ne fonctionne pas :p

catyoh
Auteur
namespace App\Table;

use Core\Table\Table;

class ArticleTable extends Table {

A quel endroit de la vidéo c'est normal que mon code ne fonctionne pas ?

Ahh je vois, et bien Grafikart a eu la même erreur que toi ;)
Faudra remplacer par:

namespace App\Table;

class ArticleTable extends \Core\Table\Table {

Et peut être d'autres modifs à faire j'ai oublié (j'ai juste survollé rapidement la vidéo)

Bref continues la vidéo, tu as bien suivi le tuto tu n'a rien manqué, c'est juste que Grafikart a planté le code et il va réparer plus tard :p

catyoh
Auteur

Le namespace Core\Table\Table est dans le use. J'ai maintes fois re- re- retesté ça.
A quel moment de la vidéo dis-tu que Grafikart répare ça ?

C'etait vers les 20minutes, mais ou iou ia bien remis en use finalement :/
Mmmh après la vidéo est peut être ancienne et PHP a changé de fonctionnement, essayes de mettre use \Core\Table avec un backslash en base pour bien lui faire comprendre que tu ne veux pas que ton use partes du dossier app, parce que du coup ton erreur de Core c'est bien la même que l'entity de la vidéo, cad le code qui essayes de passer par une Core à partir de App, donc un backslash qui forcerait le code à retourner à la racine serait bon
(Bon après les anciens tuto sur grafikart c'est assez compliqué parce que avant il ne préparait pas le tuto, il attaquait le code en freestyle, peut être par choix pour nous mettre en condition réelle, mais du coup ça complique les tutos)

catyoh
Auteur

J'ai déjà essayé use \Core\Table\Table; use \Core\Table, etc...
Je suis bien d'accord, c'est compliqué. Mais apparemment, y'en a qui y arrivent :)) et pas moi :((
Je pense que je vais laisser tomber...

catyoh
Auteur

Est-ce que ça pourrait être dans la classe App que ça coince, dans la méthode getTable : $class_name = '\\App\\Table\... ?

public function getTable($name) {
        $class_name = '\\App\\Table\\'.ucfirst($name).'Table';
        return new $class_name($this->getDb());
    }

Mmmmh non en principe toutes les Tables sont dans App, et getTable ne récupère que les classes de Table, pas la class Table de Core :/

Je dirais plutot que le problème viendrait de l'autoload App qui essaye de charger les classes se trouvant dans le Core
Peut etre en vérifiant dans l'autoload App que c'est bien une classe App il laissera la place à l'autoload Core:

static function autoload($class_name) {
        if (class_exists('\App\' . $classname) {
            if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
                $class_name = str_replace(__NAMESPACE__.'\\','', $class_name);
                var_dump(__DIR__.'\\'.$class_name); // var_dump 1
                require __DIR__.'\\'.$class_name.'.php';
            }
            else {
                var_dump(__DIR__.'\\'.$class_name); // var_dump 2
                require __DIR__.'\\'.$class_name.'.php';
            }
        }
    }

Comme ca si c'est une class Core, l'autoload App ne va même pas essayer de le charger, et ne va pas se plaindre d'échouer

catyoh
Auteur

Ca plait pas du tout à mon autoload de lui ajouter un "if(class_exists(...))", ça boucle sans fin, tout comme quand on supprime le "else".

Okay je vois, donc en gros l'autoloader Core n'est jamais utilisé :o

Dans ton autoload App tu a ça dans le else:

    else {
        require __DIR__.'\\'.$class_name.'.php';
    }

Faut remplacer par:

    else {
        require ROOT .'\\Core\\'.$class_name.'.php';  // \\Core\\Table\\Table
    }

Ou bien:

    else {
        require __DIR__.'\\..\\Core\\'.$class_name.'.php'; // \\App\\..\\Core\\Table\\Table, on va sur App, mais on remonte un cran en arriere
    }

Et si ça fonctionne, ça veut dire que l'autoloader Core ne sert à rien :o

En gros ton plantage se trouve dans le else, ton autoloader ne trouver pas la classe core parce que c'est un autoloader App, du coup il bascule dans le else, et else essaye de lancer
require __DIR__.'\\'.$class_name.'.php';, ce qui se traduit par App\\Core\\Table\\Table car __DIR__ retourne \\App, eeeet c'est pas bon

catyoh
Auteur

Eureka ! J'ai enfin trouvé le code qui convient. Voir https://nouvelle-techno.fr/actualites/live-coding-php-oriente-objet-namespace-et-autoload
Il fallait utiliser if (file_exists()) et non if (class_exists()). Du coup, le else ne sert à rien.

static function autoload($class_name) {
        if (strpos($class_name, __NAMESPACE__.'\\') === 0) {
            $class_name = str_replace(__NAMESPACE__.'\\','', $class_name);
            if(file_exists(__DIR__.'\\'.$class_name.'.php')) {
                require __DIR__.'\\'.$class_name.'.php';
            }
        }
}

Ah oui c'est le require qui plante, pas l'appel de classe :X
Super c'est enfin débloqué! :D Tu pourras passer à la suite de ta formation bien joué ^^

catyoh
Auteur

Encore merci de ton soutien sans faille :)) J'aurais abandonné sinon, et c'est dommage parce que quand même on apprend plein de choses grace à grafikart !
Comment on clôture un sujet ?