projetAcoBDDv30.03.26

This commit is contained in:
Aco
2026-03-30 16:10:26 +02:00
parent fedbf51c91
commit 455eee67f7
42 changed files with 2018 additions and 0 deletions

View File

View File

@@ -0,0 +1,44 @@
<?php
$db_name = "gamesdb" ; // Nom de la base de données (pré-existante)
$db_host = "127.0.0.1" ; // Si le serveur MySQL est sur la machine locale
$db_port = "3306" ; // Port par défaut de MySQL
// Informations d'authentification de votre script PHP
$db_user = "gameuse" ; // Utilisateur par défaut de MySQL (... à changer)
$db_pwd = "MyStrongPassword123!" ; // Mot de passe par défaut pour l'utilisateur root (.. à changer !!!)
//GRANT ALL PRIVILEGES ON gamesdb.* TO 'gameuse'@'localhost';
//gameuse'@'localhost'
//MyStrongPassword123
// Connexion à la BDD
//j'ai fabriquer cet utilisateur spécialement pour cette exo sur le serv
//mysql
// Connexion à la BDD
// Connexion à la BDD
try{
// Agrégation des informations de connexion dans une chaine DSN (Data Source Name)
$dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
// Connexion et récupération de l'objet connecté
$pdo = new PDO($dsn, $db_user, $db_pwd);
}
// Récupération d'une éventuelle erreur
catch (\Exception $ex){ ?>
<!-- Affichage des informations liées à l'erreur-->
<div style="color: red">
<b>!!! ERREUR DE CONNEXION !!!</b><br>
Code : <?= $ex->getCode() ?><br>
Message : <?= $ex->getMessage() ?>
</div><?php
// Arrêt de l'exécution du script PHP
die("-> Exécution stoppée <-") ;
}
// Poursuite de l'exécution du script ?>
<div style="color: green">Connecté à <b><?= $dsn ?></b></div> <?php ?>

View File

@@ -0,0 +1,156 @@
-- MySQL dump 10.13 Distrib 5.5.27, for Win64 (x86)
--
-- Host: localhost Database: cinema
-- ------------------------------------------------------
-- Server version 5.5.27
-- note pour l'execution, 'sur mon ordinateur personnel' dans le terminal
-- sudo mysql --local-infile=1 -p < codemysql/basedonneee.sql (obliger de sudo pour que ca marche sur mon ordi perso)
-- et aussi sudo mysql -p pour juste lancer
-- comment faire tourner mon code sur workbench
-- j'ai modifier les chemins des loads data
-- pour utiliser un chemin relatif* (car chemin relatif ne marche pas dans
-- workbench, mais fonctionne dans les fichiers de script. (il doit y avoir une option pour sur WB)
DROP DATABASE IF EXISTS siterecette;
CREATE DATABASE siterecette;
DROP USER IF EXISTS 'admin'@'localhost';
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'adminpass123';
GRANT ALL PRIVILEGES ON siterecette.* TO 'admin'@'localhost';
USE siterecette;
--
-- Table structure for table acteur
--
DROP TABLE IF EXISTS Recette;
CREATE TABLE Recette (
num_recette INT NOT NULL,
titre_recette VARCHAR(60) NOT NULL,
slug VARCHAR(200) NOT NULL,
description_recette VARCHAR(1000) NOT NULL,
photo VARCHAR(200) NOT NULL,
publication_date DATE NOT NULL,
temps_de_preparation INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Table structure for table realisateur
--
DROP TABLE IF EXISTS Tag;
CREATE TABLE Tag (
num_tag INT NOT NULL,
nom_tag VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Table structure for table salle
--
DROP TABLE IF EXISTS Ingredient;
CREATE TABLE Ingredient (
num_ingredient INT NOT NULL,
nom_ingredient INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Table structure for table film
--
DROP TABLE IF EXISTS Listeingredient;
CREATE TABLE Listeingredient (
num_recette INT NOT NULL,
num_ingredient INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Table structure for table projection
--
DROP TABLE IF EXISTS Referencetag;
CREATE TABLE Referencetag (
num_recette INT NOT NULL,
num_tag INT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS User;
CREATE TABLE User (
num_user INT NOT NULL,
username VARCHAR(20) NOT NULL,
userpassword VARCHAR(60) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- CONTRAINTES
--
-- en utilisant les 'alter table' sinon on peut directement les specifier dans les create table
ALTER TABLE Recette
ADD CONSTRAINT pk_recette PRIMARY KEY (num_recette);
ALTER TABLE Ingredient
ADD CONSTRAINT pk_ingredient PRIMARY KEY (num_ingredient);
ALTER TABLE Tag
ADD CONSTRAINT pk_tag PRIMARY KEY (num_tag);
ALTER TABLE User
ADD CONSTRAINT pk_user PRIMARY KEY (num_user);
ALTER TABLE Listeingredient
ADD CONSTRAINT fk_numrecette_ingredient FOREIGN KEY (num_recette) REFERENCES Recette (num_recette),
ADD CONSTRAINT fk_recette_numingredient FOREIGN KEY (num_ingredient) REFERENCES Ingredient (num_ingredient);
ALTER TABLE Referencetag
ADD CONSTRAINT fk_numrecette_tag FOREIGN KEY (num_recette) REFERENCES Recette (num_recette),
ADD CONSTRAINT fk_recette_numtag FOREIGN KEY (num_tag) REFERENCES Tag (num_tag);
-- si besoin exemple de chargement csv
-- LOAD DATA LOCAL INFILE 'nom.csv'
-- INTO TABLE nom
-- FIELDS TERMINATED BY ','
-- ENCLOSED BY '"'
-- LINES TERMINATED BY '\n'
-- IGNORE 1 ROWS;
-- les chemin ./filename.csv ne marche que dans le terminal avec le cli mysql via la commande $
-- select * from column;
INSERT INTO Recette (num_recette,titre_recette,slug,description_recette,photo,publication_date,temps_de_preparation)
values(1,"nomdrecette","slugdpage","descriptiondrecette","urldephoto",'2023-12-31',500),
(2,"nomdrecette","slugdpage","descriptiondrecette","urldephoto",'2023-12-31',500),
(3,"nomdrecette","slugdpage","descriptiondrecette","urldephoto",'2023-12-31',500);
-- select * from Recette;
-- INSERT INTO Tag (num_tag,nom_tag)
-- values(1,"nomdtag");
-- INSERT INTO Referencetag(num_recette, num_tag)
-- values(1,1);
-- INSERT INTO Ingredient(num_ingredient,nom_ingredient)
-- values(2,2);
-- INSERT INTO Listeingredient(num_recette,num_ingredient)
-- values(1,2);

View File

@@ -0,0 +1,16 @@
quand on créer une recette
(le but c'est que quand on en créer une
on est en même temps au moins une instance d'association
qui enregistre cette recette et ses ingrédients simultanément
de manière obligatoire)
idée 1 : on ignore l'obligation d'associer une liste d'ingredients a une recette à sa création.
idée 2 : les ingredients d'une liste d'ingredients qui est associés a une recette sont représenter comme une suite de caractère en une ligne séparer par des espace ce qui ferait un seul élément respectant la normalisation 2NF
idée 3 : ajouter des règles de contraintes à la créations d'une recette par exemple quand on créer une recette il faut que ...
soit dans la base de données soit juste dans le backend
idée ... :

45
basededonnee/entité.txt Normal file
View File

@@ -0,0 +1,45 @@
recettes ID
Titre
Slug
Description
Photo
Date d'ajout
liste-recettes (entités)
-Id listerecettes
Recette-Ingrédients
ID_Recette
ID_Ingrédient
Recette-Tags
ID_Recette
ID_Tag
Ingrédients
ID
Nom
Image
Tag
ID
Nom
Utilisateurs
ID
Username
MDP (Hashé, bien entendu...)
. Permission (utilisateurs)

103
basededonnee/test.drawio Normal file
View File

@@ -0,0 +1,103 @@
<mxfile host="65bd71144e">
<diagram id="i3f546belnF2-wjsbEOy" name="Page-1">
<mxGraphModel dx="1489" dy="755" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="Recette" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;html=1;" vertex="1" parent="1">
<mxGeometry x="300" y="232" width="170" height="182" as="geometry"/>
</mxCell>
<mxCell id="3" value="(PK)idrecette" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="26" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="4" value="titrerecette varchar(60)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="52" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="14" value="slug varchar(200) (idweb)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="78" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="15" value="Description varchar(1000)&amp;nbsp;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="104" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="16" value="photo (url) varchar(200)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="130" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="17" value="publicationdate DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry y="156" width="170" height="26" as="geometry"/>
</mxCell>
<mxCell id="6" value="Referencetag" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="320" width="111" height="78" as="geometry"/>
</mxCell>
<mxCell id="8" value="(FK)idrecette" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="6">
<mxGeometry y="26" width="111" height="26" as="geometry"/>
</mxCell>
<mxCell id="9" value="(FK)idtag" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="6">
<mxGeometry y="52" width="111" height="26" as="geometry"/>
</mxCell>
<mxCell id="10" value="Tag" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;html=1;" vertex="1" parent="1">
<mxGeometry x="-60" y="340" width="121" height="78" as="geometry"/>
</mxCell>
<mxCell id="11" value="(PK)idtag" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="10">
<mxGeometry y="26" width="121" height="26" as="geometry"/>
</mxCell>
<mxCell id="19" value="nomtag varchar(30)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="10">
<mxGeometry y="52" width="121" height="26" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="endArrow=none;html=1;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="10" target="6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="410" as="sourcePoint"/>
<mxPoint x="410" y="360" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" value="" style="endArrow=none;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="6" target="2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="300" as="sourcePoint"/>
<mxPoint x="290" y="240" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="28" value="1,n" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="50" y="334" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="31" value="1,1" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="250" y="238" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="338" value="" style="endArrow=none;html=1;exitX=0.982;exitY=1.115;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;exitPerimeter=0;" edge="1" parent="1" source="354" target="2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="80" as="sourcePoint"/>
<mxPoint x="310" y="250" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="339" value="1,1" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="280" y="202" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="341" value="" style="endArrow=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=-0.018;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="354" target="342">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-5" y="90" as="sourcePoint"/>
<mxPoint x="60" y="130" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="342" value="Ingredient" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;html=1;" vertex="1" parent="1">
<mxGeometry x="-40" y="190" width="121" height="78" as="geometry"/>
</mxCell>
<mxCell id="343" value="(PK)idingredient" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="342">
<mxGeometry y="26" width="121" height="26" as="geometry"/>
</mxCell>
<mxCell id="344" value="nomtag varchar(30)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="342">
<mxGeometry y="52" width="121" height="26" as="geometry"/>
</mxCell>
<mxCell id="345" value="1,n" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry y="160" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="351" value="Listeingredient" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="70" width="111" height="78" as="geometry"/>
</mxCell>
<mxCell id="353" value="(FK)idrecette" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="351">
<mxGeometry y="26" width="111" height="26" as="geometry"/>
</mxCell>
<mxCell id="354" value="(FK)idingredient" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;html=1;" vertex="1" parent="351">
<mxGeometry y="52" width="111" height="26" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

57
projetphp/README.md Normal file
View File

@@ -0,0 +1,57 @@
# Les recettes des Papis
## Installation
- Décompressez l'archive du site.
- Pointez votre domaine sur le dossier "public".
- Modifiez les fichiers de configuration dans "config".
## Informations pratiques sur le PHP.
- Ce site utilise le principe des routes. Afin de ne pas avoir x fichiers publiques identiques pour chaque page.
- Tout est géré par le fichier public/index.php.
- index.php va charger le Kernel (Noyau) de l'application.
- Le noyau fonctionne sur le principe d'élément unique. La méthode ``Kernel::getInstance()`` va permettre d'avoir l'unique instance de la classe Kernel.
- Une fois ``Kernel::getInstance()->init()`` déclenché, le noyau enregistre l'autoloading des fichiers, puis enregistre la configuration.
- La configuration sont des fichiers dans le dossier config/ qui vont renvoyer un tableau clé/valeur. Exemple
```php
// Fichier config/test.php
<?php return [
'cle_1' => "Val1",
'cle_2' => "Val2",
// ...
] ?>
```
- Les fichiers de configuration sont chargés via la méthode ``ConfigFactory::loadConfigFile()`` qui va vérifier si le fichier de configuration existe et le charge en conséquence.
- On enchaine ensuite sur le Router.
- Le Router est une classe utilitaire qui va permettre de faire le lien entre la route saisie ``/test/...`` de l'utilisateur et les routes enregistrés par le site.
- Le Router va d'abord commencer par récupérer la route voulue par l'utilisateur.
- Puis, le router va rechercher tous les Controllers existants.
- Un Controller est une classe qui va permettre de définir les routes et de dire, qu'est-ce qui se passe quand je prends cette route. Les routes du Controller sont définis par la methode ``Controller::defineRoutes()``
- Pour définir les routes dans la méthode précédente, vous pouvez soit créer un objet ``Route`` en remplissant tous les champs, ou bien la méthode ``Controller::Route()`` qui va préremplir certains champs.
- Exemple de Controller
```php
class TestController extends Controller {
public static function defineRoutes(): array {
return [
self::Route( routeUrl: '/test', routeName: "Test", routeAction: "test" ),
new Route( routeUrl: "/test2", routeName: "Test2", routeController: "TestController", routeAction: "test2", routeMethods = [ 'GET' ] )
];
}
public function test(){
echo "Je suis déclenché lorsque j'atteinds la route /test";
}
public function test2(){
echo "Je suis déclenché lorsque j'atteinds la route /test2 !!!";
}
}
```
- Retournons au routeur, pour chercher les controllers, il va scanner les fichiers du dossier src/Domain avec un Iterator Récursif. (Pour se simplifier un peu la tâche.)
- Il va traiter le nom du chemin absolu pour obtenir le nom de la classe, puis va vérifier si cette classe est une sous-classe de Controller.
- Après avoir trouvé tous les Controllers, il va récupérer toutes les routes de chaque controller.
- Enfin, il va vérifier si la route utilisateur correspond à une des routes dans sa liste, si ça correspond, le Router va charger la méthode ``new {RouteController}()->{RouteAction}()``
- Si par exemple, le router trouve la route ``/test`` voulu par l'utilisateur, il va déclencher la méthode ``new TestController()->test()``.

53
projetphp/config/BDD.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
class BDD{
private static ?PDO $pdo=null;
public const DB_NAME = 'siterecette';
private static string $db_name=BDD::DB_NAME;
public static function getPDO(){
if(self::$pdo==null){
$db_host = "127.0.0.1" ; $db_port = "3306" ;
// identifiant
$db_user = "admin" ; $db_pwd = "adminpass123" ;
// connection a la database
try{
$dsn = 'mysql:dbname=' . self::$db_name . ';host='. $db_host. ';port=' . $db_port;
$pdoo = new PDO($dsn, $db_user, $db_pwd);
}
//erreur?
catch (\Exception $ex){ ?>
Code : <?= $ex->getCode() ?><br> Message : <?= $ex->getMessage() ?>
<?php
die("message d'arreet connexion stopee") ;}
self::$pdo = $pdoo;
}
return self::$pdo;
}
public function getname(){
return self::$db_name;
}
}
?>

View File

@@ -0,0 +1,8 @@
<?php
return [
'website_url' => 'http://127.0.0.1:8080/',
'website_name' => 'Les recettes des Papis',
'website_path' => APP_ROOT,
];

View File

@@ -0,0 +1,6 @@
<?php
return [
'{int}' => '([0-9]+)',
'{chars}' => '([A-Za-z]+)',
'{string}' => '([0-9A-Za-z]+)',
];

View File

@@ -0,0 +1,4 @@
<?php
return [
'base_view_skeleton' => 'base'
];

View File

@@ -0,0 +1,182 @@
/* Sommaire :
- body et html
- Header et ses contenues
- Body contenue
- Content
- Page de présentation des recettes
- Page d'une recette
- Footer
*/
/*Tout*/
html, body{
height: 100%;
text-align: center;
/*font-size: 98%;*/
}
/*Header et son contenu*/
#header {
display: flex;
flex-direction: row;
padding: 3px;
padding-left: 2%;
padding-bottom: 10px;
background-color: blanchedalmond;
}
#logo {
background-color: aqua;
background-image: url("Logo.jpg");
/*background:no-repeat;*/
height: 103px;
width: 141px;
}
.logo{
height: 100%;
width: 100%;
border: 1px white solid;
border-radius: 4px;
box-shadow: 1px 1px 1px black;
}
nav {
flex: 9;
text-align: initial;
letter-spacing: 2px;
padding: 10px;
}
.nav-list {
list-style-type: none;
margin: 0;
padding: 0;
background-color: rgb(255, 217, 160);
display: flex;
border: 1px solid rgba(0,0,0,.125);
border-radius: 40px;
}
.nav-element {
line-height: 2.5;
padding: 10px;
}
/*Body et son contenu */
body {
background-color: blanchedalmond;
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
height: 100%;
}
.sidebar {
background-color: aquamarine;
border: 1px solid rgba(0,0,0,.125);
border-radius: 10px;
width: 128px;
}
.main-body {
display: flex;
flex-direction: row;
overflow: auto;
flex: 1;
background-clip: border-box;
padding-right: 5%;
padding-left: 5%;
}
/* content : le cadre de base du centre de notre site
*/
.content {
background-color: #ffe4bb;
text-align: left;
width: 100%;
height: 100%;
border: 1px solid rgba(0,0,0,.125);
border-radius: 6px;
overflow: auto;
}
/*Recettes :
représente le contenue centrale de la page.
*/
.recettes {
--UneVariable: calc( 100vw / 500 );
display: grid;
grid-template-columns: repeat(var(--UneVariable), 1fr);
grid-gap: 10px;
grid-auto-rows: minmax(300px, auto);
grid-auto-columns: auto;
}
/* Classe recette icone :
C'est la classe de chaques éléments de notre liste de recette
*/
.recette-icone{
border: 3px solid rgba(0,0,0,.125);
border-radius: 40px;
text-align: left;
padding: 15px;
vertical-align: middle;
box-shadow: 3px 5px 5px black;
width: 500px;
}
.recette-preview-image{
max-width: calc( 10vh + 10vw );
max-height: calc( 10vh + 10vw );
border: 1px solid rgb(252, 223, 57);
border-radius: 25px;
}
/*Ici commence le CSS pour la page de chaques recettes */
/*contenue*/
.recette-content {
display: flex;
flex-direction: column;
}
/*titre*/
.recette-title {
text-align: center;
}
/*image*/
.recette-image {
background-color: red;
aspect-ratio: 1/1;
max-height: calc( 30vh + 30vw );
max-width: calc( 30vh + 30vw );
border: 1px solid white;
border-radius: 30px;
}
.recette-div-image {
display: flex;
justify-content: center;
}
/*description*/
.recette-desc {
padding : 10px;
}
/*Footer et son contenue*/
footer{
padding: 25px;
background: rgb(11, 189, 144);
color: white;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -0,0 +1,28 @@
<?php
use App\Kernel;
if( !defined( 'APP_ROOT' ) )
/**
* Définit la racine de l'application en chemin absolue.
*/
define( 'APP_ROOT', realpath( __DIR__ . '/../') . '/' );
//require_once '../src/Kernel.php';
//Kernel::start();
echo "bonjour";
require '../config/BDD.php';
require '../src/Domain/Repository.php';
require_once '../src/Domain/Recettes/Recettes.php';
require '../src/Domain/Recettes/RecettesRepository.php';
$bd = new BDD();
$test = new RecettesRepository($bd->getpdo(),$bd->getname());
//public static function add(int $num_recette, string $titre_recette, string $slug, string $description_recette, string $photo, string $publication_date, int $temps_de_preparation ): Bool{
$test::add(5,"a",'a','a','a','2028-12-31',1);
$recetest = $test::getAll();
echo $recetest->slug;

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Domain;
use App\Http\Route;
abstract class Controller {
/**
* Va permettre de créer une route en préremplissant certains arguments.
* Pour les paramètres, veuillez voir le constructeur d'une Route.
*
* @param mixed ...$args
* @return Route
*
* @see Route::__construct()
*/
public static function Route( ...$args ): Route {
$defaults = [
'routeController' => static::class,
'routeMethods' => [ 'GET' ],
'pageHeadTitle' => 'Page',
];
$args = array_merge($defaults, $args);
return new Route( ...$args );
}
/**
* Permet de définir les routes du controller sous le format d'une liste d'objets Route.
* Vous pouvez utiliser la method self::Route() pour préremplir des champs.
*
* @return Route[]
* @see self::Route()
*/
abstract public static function defineRoutes(): array;
}

View File

@@ -0,0 +1,12 @@
<?php
class Model{
}
?>

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Domain\Pages;
use App\Domain\Controller;
use App\Infrastructure\View;
/**
* Controller pour les pages sans lien avec un contenu spécifique.
* Exemple : Page d'accueil.
*/
class PagesController extends Controller {
public static function defineRoutes(): array
{
return [
self::Route( routeUrl: '/', routeName: 'home', routeAction: 'index', pageHeadTitle: "Home Page" ),
self::Route( routeUrl: '/test/{int}/', routeName: 'test', routeAction: 'test' ),
];
}
/**
* Route de la page d'accueil.
* @return void
*/
public function index(): View {
return new View( 'home', [ 'ok' => 'bla' ] );
}
public function test( string $id ): void {
echo "Coucou" . $id;
}
}

View File

@@ -0,0 +1,33 @@
<?php
class Recettes { // extends Model {
public int $num_recette;
public string $titre_recette; //inférieur ou égal a 60
public string $slug; //max = 200
public string $description_recette; // max 1000
public string $photo; // max 200, url
public string $publication_date; //une date
public int $temps_de_preparation; //en minute;
//public DateTime date;
public string $column;
public function __construct(int $num_recette, string $titre_recette, string $slug,
string $description_recette, string $photo, string $publication_date, int $temps_de_preparation){
$this->column="Recette";
$this->num_recette=$num_recette;
$this->titre_recette=$titre_recette;
$this->slug=$slug;
$this->description_recette=$description_recette;
$this->photo=$photo;
$this->publication_date=$publication_date;
$this->temps_de_preparation=$temps_de_preparation;
}
}
?>

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Domain\Recettes;
use App\Domain\Controller;
class RecettesAPIController extends Controller {
public static function defineRoutes(): array
{
return [
self::Route( routeUrl: '/api/recettes/list', routeName: 'api->recettes->list', routeAction: 'list', routeMethods: ['POST'] ),
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Domain\Recettes;
use App\Domain\Controller;
use App\Http\JSONResponse;
use App\Infrastructure\View;
class RecettesController extends Controller {
public static function defineRoutes(): array
{
return [
self::Route( routeUrl: '/recettes', routeName: 'recettes->index', routeAction: 'index', pageHeadTitle: 'Liste des recettes' ),
self::Route( routeUrl: '/recettes/{string}', routeName: 'recettes->show', routeAction: 'show', pageHeadTitle: 'Recette' ),
];
}
public function index(): View {
return new View( 'recettes/index', [] );
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Domain\Recettes;
use App\Domain\Controller;
class RecettesManagementController extends Controller {
public static function defineRoutes(): array
{
return [
self::Route( routeUrl: '/recettes/create', routeName: 'recettes->create', routeAction: 'create' ),
self::Route( routeUrl: '/recettes/edit/{int}', routeName: 'recettes->edit', routeAction: 'edit' ),
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
class RecettesRepository extends Repository{
private static string $nomBDD=BDD::DB_NAME;
private static string $tablename="Recette"; //tablename
private static string $attributesname="(num_recette,titre_recette,slug,description_recette,photo,publication_date,temps_de_preparation)";
private static string $idname="num_recette";
public function __construct(){
}
public static function getAll(): ?array{
return parent::selectgetAll(self::$tablename);
}
public static function getByID($id): ?Recettes{
return parent::selectgetByID($id,self::$tablename,self::$idname);
}
//on peut passer un array (ou objet sous forme (array)objet) en paramètre à la place de tout les autres paramètres avec (...array) au choix, pas de tableaux en paramètre normal pour éviter d'envoyer un mauvais tableaux
//ca généralise quand même si chaque array pour différentes entitees sont scannee comme parametre car chacun ont leurs propre attributs
public static function add(int $num_recette, string $titre_recette, string $slug, string $description_recette, string $photo, string $publication_date, int $temps_de_preparation ): Bool{
(array)$attributes=[ $num_recette, $titre_recette, $slug, $description_recette, $photo, $publication_date, $temps_de_preparation];
return parent::insertadd($attributes,self::$tablename,self::$attributesname,self::$idname,1); //le '1' car isentity, on vérifie que existe pas dejaa avec cet id
return false;
}
}
?>

View File

@@ -0,0 +1,99 @@
<?php
class Repository{
//private static string $nomBDD;
//private static string $attributesname;
//private static ?PDO $pdo;
//public function __construct(?PDO $pdoo, string $nombdd){
//self::$pdo=$pdoo; self::$nomBDD = $nombdd; self::$column="Recette";
//self::$attributesname="(num_recette,titre_recette,slug,description_recette,photo,publication_date)";
//}
//les infos nécessaires sont passé par les classe filles
public static function selectgetAll($tablename): ?array{
$sql = "SELECT * FROM " . $tablename;
$pdo=BDD::getPDO();
$statement = $pdo->prepare($sql) ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;
$results = $statement->fetchAll(PDO::FETCH_OBJ) ;
//print_r($results);
if(empty($results))return null;
return (array)$results;
}
public static function selectgetByID($id,$tablename,$idname): ?Recettes{
$sql = "SELECT * FROM " . $tablename . " WHERE " . $idname ." = " . $id;
print_r($sql);
$pdo=BDD::getPDO();
$statement = $pdo->prepare($sql) ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;
$results = $statement->fetchAll(PDO::FETCH_OBJ);
$valuearray = (array)$results[0];
//print_r($valuearray);
//$results = un array de 1 dimension de taille 1 elt avec chaque attribut $result[0]->attribut echo print_r($results[0]->titre_recette);
if(empty($valuearray))return null;
return new Recettes(...$valuearray); // '...' unpack l'array et comme les clés correspondent a celles du constructeurs ça marche.
}
public static function insertaddall(array $attributesattributes,string $tablename,string $attributesname, string $idname): bool{
$pdo = BDD::getPDO();
$sql = "INSERT INTO " . $tablename . " " . $attributesname;
//je vais utiliser bind value car en fonction de quel entitee arrive on a des str ou des int c'est plus dur de generaliser si nécessaire de gerer des ,'str',int,int,'str', etc.
if(empty($attributesattributes))return false;
$attribtag="val"; $interativ=0;
$attribbind=" VALUES (";
foreach($attributesattributes[0] as $a){
$interativ++;
$attribtagn=$attribtag . $interativ;
if($interativ>1){$attribbind = $attribbind . ", ";}
$attribbind = $attribbind . ":" . $attribtagn;
}
$attribbind = $attribbind . ")";
$query = $sql . $attribbind;
$statement = $pdo->prepare($query);
// Liaison des paramètres à des valeurs
$interativ=0;
foreach($attributesattributes as $attributes)
foreach($attributes as $attribute){
$interativ++;
$attribtagn=$attribtag . $interativ;
$statement->bindValue($attribtagn, $attribute);
}
$statement->execute() or die(var_dump($statement->errorInfo())) ;
return true;
}
public static function insertadd(array $attributes,$tablename,$attributesname, $idname, $isentity): bool{
// vérification id n'existe pas déjà :
if($isentity){
$isitalreadyusedid = self::selectgetById($attributes[0],$tablename,$idname); //le dernier param = id, accessible ainsi car simple array des attributs d'une seule entitee,
// à modifier pour insertall si on fait la verif dedans mais distinguer les array attributs d'entitee et d'association, la verif des associations doit être autre (si idtable1 combinee a idtable2 existe deja dans un même enregistrement)
if(!empty($isitalreadyusedid)) return false;}
//je vais utiliser bind value car en fonction de quel entitee arrive on a des str ou des int c'est plus dur de generaliser si nécessaire de gerer des ,'str',int,int,'str', etc.
//le mieux serait de mettre tout le code de insertadd dans une fonction insertadds qui gère une multiinsertion, insertadd appelle insertadds pour un seul elt.
(array)$attributesattributes=[$attributes];
return self::insertaddall($attributesattributes,$tablename,$attributesname,$idname);
}
}
?>

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Domain\Utilisateurs;
use App\Domain\Controller;
use App\Helpers\Authentification;
use App\Http\JSONResponse;
use App\Http\Request;
class AuthentificationController extends Controller {
public static function defineRoutes(): array {
return [
// Public routes.
self::Route( routeUrl: '/login', routeName: 'login', routeAction: 'loginForm', pageHeadTitle: 'Connexion' ),
// API Routes.
self::Route( routeUrl: '/api/auth', routeName: 'api->auth', routeAction: 'auth', routeMethods: ['POST'] ),
self::Route( routeUrl: '/api/auth/logout', routeName: 'api->auth->logout', routeAction: 'logout', routeMethods: ['POST'] ),
];
}
public function login(): View {
return new View( 'login' );
}
public function auth(): JSONResponse {
Request::setCORS();
$username = Request::get( 'username' );
$password = Request::get( 'password' );
// TODO : Récupération de l'utilisateur et verify_password.
$userId = 1;
Authentification::loginUser( $userId );
JSONResponse::sendSuccess( [ 'user_id' => $userId ] );
}
public function logout(): JSONResponse {
if( !Authentification::isLoggedIn() ) {
return JSONResponse::sendError( [ 'message' => 'Alrady disconnected' ] );
}
Authentification::destroySession();
return JSONResponse::sendSuccess( [ 'message' => 'Logged out' ] );
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Exceptions;
/**
* Exception qui se déclenche lorsqu'un fichier de configuration n'a pas pu être chargé.
*
* - Peut se déclencher si un fichier de configuration non voulu.
*/
class ConfigFailedLoadingException extends \Exception {
/**
* Chemin vers le fichier de configuration manquant.
* @var string
*/
public private(set) string $configFilePath;
/**
* Constructeur.
* @param string $configFilePath Le fichier de configuration manquant.
*/
public function __construct( string $configFilePath ) {
$this->configFilePath = $configFilePath;
$this->message = "Failed to load configuration file '{$configFilePath}'.";
parent::__construct();
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Exceptions;
class InvalidRouteException extends \Exception {
private string $clientRoute;
public function __construct(string $clientRoute)
{
$this->clientRoute = $clientRoute;
$message = "{$clientRoute} doesn't exist";
parent::__construct($message, 404);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Exceptions;
use Throwable;
class InvalidViewException extends \Exception {
public function __construct( string $viewName )
{
parent::__construct( "View {$viewName} does not exist.", 500 );
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Helpers;
class Authentification {
/**
* Permet de démarrer la variable Session.
*
* @return void
*/
public static function startSession(): void {
session_start();
}
/**
* Permet de supprimer la session.
*
* @return void
*/
public static function destroySession(): void {
session_destroy();
}
/**
* Permet de connecter un utilisateur.
*
* @param int $userId
* @return void
*/
public static function loginUser( int $userId ){
$_SESSION['user'] = $userId;
}
// TODO : Complete when user.
public static function getCurrentUser() {
return $_SESSION['user'] ?? false;
}
/**
* Permet de savoir si un utilisateur est connecté ou pas.
*
* @return bool
*/
public static function isLoggedIn(): bool {
return self::getCurrentUser() !== false;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Helpers;
/**
* Classe utilitaire contenant des méthodes pour charger automatiquement les classes.
*/
class AutoLoader {
/**
* Namespace principal de l'application.
*/
public const string PRIMARY_NAMESPACE = 'App\\';
/**
* Permet d'enregistrer la méthode _autoload_ en tant que méthode pour charger des classes.
* @return void
*/
public static function register(): void {
spl_autoload_register( [ self::class, '_autoload_' ] );
}
/**
* Permet de charger une classe.
*
* @param string $className
* @return void
*/
public static function _autoload_( string $className ): void {
if( !str_starts_with( $className, self::PRIMARY_NAMESPACE ) ) // Ne s'occupe que des classes à charger de cette application.
return;
$className = str_replace( self::PRIMARY_NAMESPACE, '', $className );
$className = str_replace( '\\', '/', $className );
require APP_ROOT . 'src/' . $className . '.php';
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Helpers;
use App\Exceptions\ConfigFailedLoadingException;
/**
* Classe outillage qui permet de charger les fichiers de configuration.
*/
class ConfigFactory {
/**
* Chemin relatif à partir de APP_ROOT du dossier contenant les fichiers de configuration.
*
* @see APP_ROOT
*/
const string CONFIG_RELATIVE_FOLDER = "config/";
/**
* Permet de charger un fichier de configuration.
*
* @param string $fileName Le nom du fichier de configuration, avec ou sans l'extension .php.
* @return array
*
* @throws ConfigFailedLoadingException
*/
public static function loadConfigFile( string $fileName ): array {
if( !str_ends_with( $fileName, ".php" ) ) {
$fileName .= ".php";
}
$filePath = APP_ROOT . self::CONFIG_RELATIVE_FOLDER . $fileName;
if( !file_exists( $filePath ) ){
throw new ConfigFailedLoadingException( "Config file '$fileName' does not exist" );
}
$configArray = require $filePath;
return $configArray;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Helpers;
/**
* Trait qui permet de désinfecter une variable.
*/
trait SanitizeTrait {
/**
* Permet de désinfecter une variable
*
* @param mixed $data
* @return mixed
*/
public static function sanitize( mixed $data ): mixed {
if( is_string( $data ) ) {
return htmlspecialchars( $data, ENT_QUOTES );
} else if( is_integer( $data ) ) {
return $data;
}
return $data;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http;
/**
* Permet de renvoyer une réponse de route au format JSON.
*/
class JSONResponse {
/**
* Les données ajoutés au fichier JSON.
* @var array|mixed
*/
public private(set) array $data;
/**
* Le code HTML de la réponse.
* @var int|mixed
*/
public private(set) int $htmlCode;
public function __construct( $data = [], $code = 200 ){
$this->data = $data;
$this->htmlCode = $code;
$this->returnResponse();
}
public function returnResponse(): never {
header( 'Content-type: application/json' );
http_response_code( $this->htmlCode );
$this->data['_status'] = $this->htmlCode;
$json = json_encode( $this->data );
echo $json;
die();
}
public static function sendSuccess( $data = [] ): self {
$data['success'] = true;
return new self( $data, 200 );
}
public static function sendError( $data = [] ): self {
$data['success'] = false;
return new self( $data, 400 );
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http;
use App\Helpers\SanitizeTrait;
use App\Kernel;
/**
* Classe utilitaire ayant plusieurs méthodes pour gérer la requête actuelle.
*/
class Request {
use SanitizeTrait;
/**
* Bloquer les CORS venant d'autres sites.
* @return void
*/
public static function setCORS(): void {
$siteUrl = Kernel::$configs['general']['website_url'];
header("Access-Control-Allow-Origin: {$siteUrl}");
}
/**
* Permet d'obtenir une variable GET et nettoyé.
*
* @param string $name
* @return mixed
*/
public static function get( string $name ): mixed {
if( !isset( $_GET[$name] ) ) {
return null;
}
return self::sanitize( $_GET[$name] );
}
/**
* Permet d'obtenir une variable POST et nettoyé.
*
* @param string $name
* @return mixed
*/
public static function post( string $name ): mixed {
if( !isset( $_POST[$name] ) ) {
return null;
}
return self::sanitize( $_POST[$name] );
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http;
/**
* Permet de définir une route précise.
*/
final class Route {
public private(set) string $routeUrl;
public private(set) string $routeName;
public private(set) string $routeController;
public private(set) string $routeAction;
public private(set) array $routeMethods;
public private(set) string $pageHeadTitle;
/**
* @param string $routeUrl Le chemin vers la route.
* @param string $routeName Le nom de la route.
* @param string $routeController Le controller qui va interpréter cette route.
* @param string $routeAction L'action du controller qui va interpréter cette route.
* @param array $routeMethods Une liste de methodes GET, POST que la page accepte.
* @param string $pageHeadTitle Le nom de la page qui sera affiché dans <title></title>.
*/
public function __construct(
string $routeUrl,
string $routeName,
string $routeController,
string $routeAction,
array $routeMethods,
string $pageHeadTitle,
){
$this->routeUrl = $routeUrl;
$this->routeName = $routeName;
$this->routeController = $routeController;
$this->routeAction = $routeAction;
$this->routeMethods = $routeMethods;
$this->pageHeadTitle = $pageHeadTitle;
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace App\Http;
use App\Domain\Controller;
use App\Exceptions\InvalidRouteException;
use App\Helpers\AutoLoader;
use App\Kernel;
use FilesystemIterator;
final class Router {
/**
* Route voulue par le client.
* @var string
*/
public private(set) static string $clientRouteString;
/**
* Informations de la route du client.
* @var Route
*/
public private(set) static Route $clientRoute;
/**
* Informations sur les arguments passés à la route.
* @var array
*/
public private(set) static array $clientRouteArguments;
/**
* Liste des controllers sorti du cache ou récupéré via fetchControllers.
* @var array
*/
private static array $controllers;
/**
* Liste des routes tirées des controllers. Rempli par la méthode fetchRoutes.
* @var Route[]
*/
private static array $routes;
/**
* Fonction principale qui va router le contenu vers la bonne méthode du bon Controller.
* @return void
*
* @throws InvalidRouteException Si la route voulue n'existe pas ou n'est pas bien formaté.
*/
public static function routeTo(): void {
self::$clientRouteString = $_SERVER['REQUEST_URI'];
self::$controllers = self::fetchControllers();
self::$routes = self::fetchRoutes();
$clientRoute = self::clientRouteExist();
if( !$clientRoute ){
throw new InvalidRouteException( self::$clientRouteString );
}
self::$clientRoute = $clientRoute;
if( !in_array( $_SERVER['REQUEST_METHOD'], self::$clientRoute->routeMethods ) ){
throw new InvalidRouteException( self::$clientRouteString );
} else {
self::executeRouteAction();
}
}
/**
* Permet de récupérer tous les controllers du dossier Domain.
* @return class-string[]
*/
private static function fetchControllers(): array {
$classes = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator( APP_ROOT . 'src/Domain', FilesystemIterator::SKIP_DOTS ),
);
foreach( $iterator as $file ){
if( $file->isFile() && $file->getExtension() === 'php' ){
$fileName = $file->getPathname();
// Transformation du chemin du fichier vers le nom complet de la classe.
$fileName = str_replace( APP_ROOT . 'src/', AutoLoader::PRIMARY_NAMESPACE, $fileName );
$fileName = str_replace( '.php', '', $fileName );
$fileName = str_replace( '/', '\\', $fileName );
/*
* Vérifie que l'on est bien sur un Controller.
* Ignore Controller car Controller n'est pas une sous-classe de Controller.
*/
if( is_subclass_of( $fileName, Controller::class ) ){
$classes[] = $fileName;
}
}
}
return $classes;
}
/**
* Récupérer toutes les routes des controllers récupéré avant.
* @return Route[]
*/
private static function fetchRoutes(): array {
$routes = [];
foreach( self::$controllers as $controllerClassString ){
$routes = array_merge( $routes, $controllerClassString::defineRoutes() );
}
return $routes;
}
/**
* Permet de savoir si la route que le client veut existe.
* @return Route|bool Retourne la route de l'utilisateur ou false si inexistante.
*/
private static function clientRouteExist(): Route|bool {
$clientRouteName = trim( self::$clientRouteString, '/' );
foreach( self::$routes as $route ){
$routeName = self::getRegexRoute( $route );
if( preg_match( $routeName, $clientRouteName, $matches ) ){
array_shift( $matches ); // On enlève la chaine complète.
self::$clientRouteArguments = $matches;
return $route;
}
}
return false;
}
/**
* Permet de remplacer les expressions définies dans la configuration par leurs équivalents Regex.
*
* @param Route $route
* @return string
*/
private static function getRegexRoute( Route $route ): string {
$routeUrl = trim( $route->routeUrl, '/' );
foreach ( Kernel::$configs['route_arguments'] as $key => $value ){
$routeUrl = str_replace( $key, $value, $routeUrl );
}
return "#^{$routeUrl}$#";
}
/**
* Va permettre d'exécuter la méthode du Controller→action() en parsant les arguments si existants.
* @return void
*/
private static function executeRouteAction(): void {
$controller = self::$clientRoute->routeController;
$method = self::$clientRoute->routeAction;
new $controller()->$method( ...self::$clientRouteArguments);
}
/**
* Permet d'obtenir l'adresse du site vers une route spécifique.
*
* @param string $routeName Le nom de la route.
* @param ...$args - Les arguments de la route, si existants.
*
* @return string Renvoie l'URL demandé ou bien la racine du site si la route n'existe pas.
*/
public static function getRouteURL( string $routeName, ...$args ): string {
foreach( self::$routes as $route ){
if( $routeName === $route->routeName ){
$i = 0;
$routeUrl = preg_replace_callback( '/\{([^}]+)}/', function( $match ) use ( $args, &$i ){
return $args[$i++] ?? "";
}, $route->routeUrl);
return rtrim( Kernel::$configs['general']['website_url'] . $routeUrl, '/' );
}
}
return Kernel::$configs['general']['website_url'];
}
public static function getAssetURL( string $assetPath ): string {
return Kernel::$configs['general']['website_url'] . 'assets/' . $assetPath;
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace App\Infrastructure;
use App\Exceptions\InvalidViewException;
use App\Http\Router;
use App\Kernel;
/**
* Permet de rendre une vue / contenu de la page.
* /!\ : Une seule vue peut-être appellé par page, hormis celle en plus pour le squelette.
*/
final class View {
/**
* Répertoire où se trouvent les vues.
*/
const string VIEW_PATH = APP_ROOT . 'views/';
/**
* Le nom du fichier de vue.
* @var string
*/
public private(set) string $viewName;
/**
* Les arguments passés à la vue.
* @var array
*/
public private(set) array $viewArgs;
/**
* La base de la vue. (Pour ne pas répéter header/footer à chaque fois).
* null correspond à pas de squelette.
*
* @var string|mixed|null
*/
public private(set) ?string $skeleton;
/**
* Utilisé par les squelettes, permet d'injecter des contenus comme le contenu de son enfant.
* @var array<string,string>
*/
public private(set) array $integratedContent;
/**
* Garde la dernière instance de vue.
* Si il y a un squelette, ce sera l'instance du squelette.
*
* @var View
*/
private static self $lastInstance;
/**
* Permet de construire les informations de la vue.
*
* @param string $viewName Le nom du fichier de vue.
* @param array $viewArgs Les arguments de la vue.
* @param bool $autoRender Si la méthode $this->render() est automatiquement exécuté.
* @param string|null $skeleton Le squelette du fichier, null correspond à aucun squelette, default correspond au
* squelette défini dans la configuration des vues.
* @param array<string,string> $integratedContent Le contenu intégré dans le nouveau squelette.
*/
public function __construct(
string $viewName,
array $viewArgs = [],
bool $autoRender = true,
?string $skeleton = 'default',
array $integratedContent = []
) {
if( !str_ends_with( $viewName, '.php' ) ) {
$viewName .= '.php';
}
$this->viewName = $viewName;
$this->viewArgs = $viewArgs;
$this->skeleton = $skeleton;
if( $this->skeleton === "default" ){
$this->skeleton = Kernel::$configs['views']['base_view_skeleton'];
}
$this->integratedContent = $integratedContent;
try {
if (!file_exists(self::VIEW_PATH . $this->viewName)) {
throw new InvalidViewException($this->viewName);
}
if ($autoRender) {
$this->render();
}
} catch (InvalidViewException $e) {
die( $e->getMessage() );
}
}
/**
* Permet de démarrer le rendu d'une vue.
* @return void
*/
public function startView(): void {
ob_start();
}
/**
* Permet de finir le rendu d'une vue.
* @return void
*/
public function endView(): void {
echo ob_get_clean();
}
/**
* Permet de faire le rendu proprement de la vue.
* @return void
*/
public function render(): void {
self::$lastInstance = $this;
if( $this->skeleton === null ){
// Si on a pas de squelette, on inclut la vue.
// pour accéder aux éléments de la vue, on utilise les méthodes statiques de cette classe.
$this->startView();
require self::VIEW_PATH . $this->viewName;
$this->endView();
} else {
// Si on a un squelette, on charge tout le contenu de la vue enfante.
$contentName = $this->viewName;
// On démarre la vue du squelette.
$base = new View( $this->skeleton, $this->viewArgs, skeleton: null, integratedContent: [ 'content' => $contentName ] );
}
}
/*
* VIEW TOOLS
*/
/**
* Permet d'injecter un contenu sauvegardé dans $this->integratedContent.
*
* @param string $integratedContentName
* @return void N'affiche rien si le contenu n'existe pas.
*/
public static function inject( string $integratedContentName ): void {
if( isset( self::$lastInstance->integratedContent[ $integratedContentName ] ) )
require self::VIEW_PATH . self::$lastInstance->integratedContent[ $integratedContentName ];
}
/**
* Permet de récupérer un argument passé à la vue.
*
* @param string $argumentName
* @return mixed null si l'argument n'existe pas.
*/
public static function arg( string $argumentName ): mixed {
if( isset( self::$lastInstance->viewArgs[ $argumentName ] ) )
return self::$lastInstance->viewArgs[ $argumentName ];
return null;
}
/**
* Permet d'intégrer un bloc à la vue.
*
* @param string $partialName
* @return void N'affiche rien si le bloc n'existe pas.
*/
public static function partial( string $partialName ): void {
if( !str_ends_with( $partialName, '.php' ) ) {
$partialName .= '.php';
}
if( file_exists( self::VIEW_PATH . 'partials/' . $partialName ) ) {
require self::VIEW_PATH . 'partials/' . $partialName;
}
}
/**
* Permet d'obtenir le titre de la page à mettre dans <title></title>
* @return string
*/
public static function getHeadTitle(): string {
$siteUrl = Kernel::$configs['general']['website_name'];
return Router::$clientRoute->pageHeadTitle . ' - ' . $siteUrl;
}
/**
* Permet d'obtenir une route
*
* @param string $routeName Le nom de la route.
* @param ...$args - Les arguments de la route, si existant.
*
* @return void Affiche uniquement.
*/
public static function routeUrl( string $routeName, ...$args ): void {
echo Router::getRouteURL( $routeName, ...$args );
}
/**
* Permet d'obtenir l'URL vers un asset (CSS,JS,Images).
* @param string $assetPath
*
* @return void
*/
public static function assetUrl( string $assetPath ): void {
echo Router::getAssetURL( $assetPath );
}
}

100
projetphp/src/Kernel.php Normal file
View File

@@ -0,0 +1,100 @@
<?php
namespace App;
use App\Exceptions\ConfigFailedLoadingException;
use App\Exceptions\InvalidRouteException;
use App\Helpers\Authentification;
use App\Helpers\AutoLoader;
use App\Helpers\ConfigFactory;
use App\Http\Router;
/**
* Classe primaire du site.
* Permet de lancer l'application.
*/
final class Kernel {
/**
* Liste des fichiers de configuration sous le format suivant :
* [ 'nom_de_la_config' -> [ 'cle" → valeur ] ]
*
* @var array<string,array>
*/
public private(set) static array $configs = [];
/**
* Instance actuelle de l'application.
* @var Kernel|null
*/
private static ?self $instance = null;
/**
* Méthode qui permet de démarrer le site.
* @return self
*/
public static function start(): self {
self::$instance = new self();
self::$instance->init();
return self::$instance;
}
/**
* Permet d'obtenir l'instance actuelle du site.
* @return self
*/
public static function getInstance(): self {
return self::$instance;
}
/**
* Constructeur.
*/
public function __construct() {
}
/**
* Permet de préparer le démarrage du site.
* Lancé automatiquement par start().
*
* @return void
* @see self::start()
*/
public function init(): void {
$this->buildAutoloader();
$this->loadConfig();
Authentification::startSession();
try {
Router::routeTo();
} catch ( InvalidRouteException $e ){
die( $e->getMessage() );
}
}
/**
* Permet de mettre en place l'Autoloader.
* @return void
*/
private function buildAutoloader(): void {
require_once 'Helpers/AutoLoader.php';
AutoLoader::register();
}
/**
* Permet de charger tous les fichiers de configuration du site.
* @return void
*/
private function loadConfig(): void {
try {
self::$configs['general'] = ConfigFactory::loadConfigFile('general');
self::$configs['route_arguments'] = ConfigFactory::loadConfigFile('route_arguments');
self::$configs['views'] = ConfigFactory::loadConfigFile('views');
} catch( ConfigFailedLoadingException $e ){
die( $e->getMessage() );
}
}
}

9
projetphp/views/base.php Normal file
View File

@@ -0,0 +1,9 @@
<?php use App\Infrastructure\View as V; ?>
<?php V::partial( 'header' ); ?>
<div class="main-body">
<?php V::inject( 'content'); ?>
</div>
<?php V::partial( 'footer' ); ?>

2
projetphp/views/home.php Normal file
View File

@@ -0,0 +1,2 @@
<h1>Coucou</h1>
<?php echo \App\Http\Router::getRouteURL( 'test', 3 ); ?>

View File

@@ -0,0 +1,14 @@
<?php use App\Infrastructure\View as V; ?>
<footer id="footer">
<div class="src">
<a href="google.com">Une source</a>
</div>
<div class="Contact">
Front end : Bousquet Sébastien
--- Back end : Thorel Benjamin
--- Base de donné : Glaudis Jordan
</div>
</footer>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<?php use App\Infrastructure\View as V; ?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo V::getHeadTitle(); ?></title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="<?php V::assetUrl( 'css/style.css' ); ?>" />
</head>
<body>
<header id="header">x
<div id="logo">
<a href="index.php">
<img src="<?php V::assetUrl( 'images/Logo.jpg' ); ?>" class="logo">
</a>
</div>
<nav>
<ul class="nav-list">
<li><a id="google" class="nav-element" href="google.com">Google</a></li>
<li><a id="unTest" class="nav-element" href="bing.com">Bing</a></li>
</ul>
</nav>
</header>

View File

@@ -0,0 +1,20 @@
<?php use App\Infrastructure\View as V; ?>
<div class="sidebar"> une sidebar</div>
<div class="content">
<div class="recettes">
<a href="recette-en-dur.php">
<div class="recette-icone">
<img class="recette-preview-image" src="random-recette.jpg">
</div>
</a>
<div class="recette-icone"> 2 </div>
<div class="recette-icone"> 3 </div>
<div class="recette-icone"> 4 </div>
<div class="recette-icone"> 5 </div>
<div class="recette-icone"> 6 </div>
<div class="recette-icone"> 7 </div>
<div class="recette-icone"> 8 </div>
<div class="recette-icone"> 9 </div>
</div>
</div>