PHP orienté objet

Le concept d'OOP

Définition
La Programmation Orientée Objet (Object Oriented Programming) est un paradigme de programmation, càd une façon de penser la programmation.
L'OOP repose sur l'idée de représenter les différents éléments d'un programme sous forme d'objets, càd un composant possédant un nom, des attributs et des capacités.
Exemple et différence
Imaginons que nous voulions représenter une voiture.
Classique
OOP
- On créer des variables, marque, vitesse, reservoir, etc.
- On crée des fonctions, accélerer, freiner, etc.
- On crée un objet voiture
- On défini des paramètres pour cet objet (marque, vitesse, réservoir)
- On défini des actions pour cet objet, accélérer, freiner
Intérets de l'OOP
Contrairement à la programmation classique, l'OOP permet de définir clairement des entités cohérentes.
On a pas des variables qui se baladent, mais un objet complet avec ses paramètres.
L'OOP permet de représenter le monde d'une façon plus "naturelle".
L'OOP facilite l'écriture d'un code modulaire.
Débuter l'OOP

La notion de classe
Avant de pouvoir créer un objet, nous allons devoir créer une classe.
Une classe, c'est un élément du programme qui définie un objet. Une sorte de moule.
Quand on parle d'un objet, on parle en fait d'une entité qui a été créée à partir d'une classe.
Imaginons que l'ont fabrique une sculpture, la classe serait le moule dans lequel on a coulé la sculpture, la sculpture serait l'objet.
Créer une classe
Pour créer une classe en PHP, on utilise le mot clef "class".
Imaginons donc que nous voulions créer une classe Pokemon (un nom de classe commence toujours par une majuscule, par convention).
<?php
class Pokemon
{
}Pour l'instant, notre classe ne sert pas à grand chose. Nous devons lui ajouter les attributs qui définissent un pokémon.
Instancier un objet
Maintenant que nous avons créé une classe pokémon, nous allons pouvoir fabriquer un pokémon avec.
Pour cela, on utilise la syntaxe new.
<?php
include './pokemon.php';
$pokemon = new Pokemon();
var_dump($pokemon);Définir des attributs
Les attributs sont les propriétés d'une entité, ce qui fait par exemple qu'un pokémon est un pokémon, et pas une plante verte.
Un attribut peut être défini soit de façon privée (private), soit de façon publique (public).
Un attribut privé n'est accessible qu'à l'intérieur de l'objet. Un attribut public est accessible depuis le reste du code.
Rendre un attribut privé permet de s'assurer qu'on ne pourra pas le modifier directement.
Pour notre pokémon, nous allons définir :
- Un total de PV - Un nom
- Un niveau - Une force
- Un type
<?php
class Pokemon
{
public $name;
public $life;
public $level;
public $type;
public $strength;
}Problème, pour l'instant ces attributs sont vides.
Définir la valeur des attributs
Pour qu'un attribut soit utile, il va falloir que nous lui donnions une valeur.
Il y a 3 façons différentes de faire cela.
1 - Définir une valeur par défaut.
2 - Définir une valeur en accédant directement à l'attribut.
3 - Passer les valeurs à utiliser via un constructeur.
Pour définir une valeur par défaut, il nous suffit de donner une valeur à l'attribut au moment de sa déclaration comme nous le ferions pour une variable.
<?php
class Pokemon
{
public $name = 'Carapuce';
public $life = 100;
public $level = 5;
public $type = 'eau';
public $strength = 10;
}Problème, on ne peux pas créer de pokémons différents.
Pour donner une valeur en accédant à l'attribut, on doit utiliser la notation "->" et donner une valeur comme à une variable.
<?php
class Pokemon
{
public $name;
...
}
$pokemon = new Pokemon();
$pokemon->name = 'Carapuce';
$pokemon->life = 100;
$pokemon->level = 5;
$pokemon->type = 'eau';
$pokemon->strength = 10;Problème, c'est long, ça nécessite un accès direct aux attributs, c'est assez limité.
Il est possible d'utiliser une meilleure méthode, le constructeur.
<?php
class Pokemon
{
public $name;
public $life;
public $level;
public $type;
public $strength;
public function __construct ($name, $life, $level, $type, $strength)
{
$this->name = $name;
$this->life = $life;
$this->level = $level;
$this->type = $type;
$this->strength = $strength;
}
}
$pokemon = new Pokemon('Carapuce', 100, 5, 'eau', 10);Pour passer les données au constructeur, il suffira de les passer comme des arguments de fonction au moment de l'invocation.
Utiliser un constructeur
Un constructeur, c'est une fonction de la classe qui va être appelée au moment d'instancier un objet. Potentiellement en lui passant des paramètres.
Pour créer un constructeur, on va créer une fonction publique nommée "__construct" dans l'objet.
Exemple, pour créer notre Carapuce.
Cette méthode à de nombreux avantages.
- Il n'est pas possible de créer un objet incomplet
- On peut facilement effectuer des traitements complexe sur les données reçues en argument
- On peut s'assurer que les données sont bien du bon type, cohérentes, etc.
- On peut garder des attributs privés
- On voit directement les attributs attendus, leur type, etc., quand on lit la classe.
Créer des méthodes
Nous avons vu comment créer des attributs pour une classe, mais comme nous l'avons dit, un objet est décrit par ses attributs, mais aussi par les actions qu'il peut effectuer.
En OOP, on parle de méthodes, càd des fonctions liées à une classe et qui peuvent notamment manipuler ses attributs.
Et comme pour les attributs, les méthodes peuvent être publiques (public) ou privées (private).
Pour définir une méthode, on cela reviens à déclarer une fonction à l'intérieur d'une classe, en précédant sa déclaration par son niveau de visibilité.
Par exemple, on va ajouter à notre pokémon une fonction pour monter de niveau.
<?php
class Pokemon
{
...
public function level_up ()
{
$this->level += 1;
$this->life += 5;
$this->strength += 2;
$level_up_text = $this->name . ' passe au niveau ' . $this->level . "\nIl gagne 5 pts de vie et 2 pts de force.\n";
echo $level_up_text;
return true;
}
}
$pokemon = new Pokemon('Carapuce', 100, 5, 'eau', 10);
$pokemon->level_up();Pour appeler une méthode, on utilise "->".
Copier un objet
Parfois, on peut souhaiter copier un objet. L'habitude nous pousserai pour cela à utiliser =, comme pour copier une variable.
Si on souhaite réellement créer une copie différente de l'objet, on dois utiliser l'instruction clone.
<?php
class Foo
{
public $val = 10;
}
$foo = new Foo();
$bar = clone $foo;
$bar->val = 5;
echo $foo->val; //Affichera 10 !<?php
class Foo
{
public $val = 10;
}
$foo = new Foo();
$bar = $foo;
$bar->val = 5;
echo $foo->val; //Affichera 5 !Or, en PHP, les objets sont TOUJOURS passés comme des références !
Prise en main

Améliorer notre pokémon
Notre pokémon actuel est trop limité, nous allons le faire évoluer pour qu'il puisse participer à des combats.
On va donc faire en sorte que notre pokémon puisse attaquer, être attaqué et que sa vie puisse changer, sans pour autant qu'on oublie sa vie d'origine.

Fabriquer une pokéball
Une fois notre pokémon prêt à se battre, nous allons créer une pokéball.
Une pokéball possède un nom.
Une pokéball possède un niveau.
Une pokéball peut être utilisée pour essayer de capturer un pokémon, mais ça ne réussi pas toujours.

Formules
Combat : Une attaque inflige : strength * (rand(900, 1100) / 1000), arrondi à l'entier sup.
Capture : Les chances de capturer un pokémon sont de : (($max_life - $life) / $max_life) * (1 + ($lvl_pokeball - $lvl_pokemon) / 25) sur 1, arrondi au centième.

OOP avancé

Utiliser des méthodes statiques
Par défaut, quand on veux utiliser la méthode d'une classe, il faut d'abord instancier un objet de cette classe.
Cela est parfaitement logique quand on souhaite accéder à des attributs d'un objet. Mais si cela n'est pas nécessaire, il pourrait être intéressant d'appeler une méthode sans avoir d'abord instancié un objet.
Bonne nouvelle, PHP propose de créer une méthode statique (static) qui permet justement de faire ça !
Par exemple, imaginons que nous voulions ajouter à notre pokémon un moyen de dire coucou. Pour cela, on a pas besoin d'accéder à ses attributs. On peut donc créer une méthode statique.
<?php
class Pokemon
{
...
public static function say_hi ()
{
echo 'Hi !' . "\n";
}
} Appeler une méthode statique
Pour appeler une méthode statique, cela se fait de deux façon différentes.
Via le nom de la classe "::" le nom de la méthode depuis le scope général.
Via self::nom_methode depuis le scope de la classe.
Exemple, dire bonjour depuis le scope général.
<?php
...
Pokemon::say_hi();Exemple, dire bonjour à l'invocation du pokémon, depuis son __construct !
<?php
class Pokemon
{
...
public function __construct ($name, $max_life, $life, $level, $type, $strength)
{
...
self::say_hi();
}
}Utiliser l'héritage
En OOP il existe un mécanisme très puissant appelé héritage.
L'héritage permet de créer une classe qui va hériter, c'est à dire récupérer les attributs et méthodes, d'une autre classe.
L'héritage permet donc de créer des objets plus spécifiques en partant d'un objet plus généraliste, sans avoir à ré-implémenter toutes ses fonctions.
Pour utiliser l'héritage, on utilise le mot clef extends.
Un exemple d'héritage serait de créer une classe pour chaque espèce de pokémon, qui héritera de la classe généraliste pokémon.
<?php
include './pokemon.php';
class Carapuce extends Pokemon
{
}
$carapuce = new Carapuce('Carapuce', 125, 125, 5, 'eau', 20);
var_dump($carapuce);Jusque là, rien de très utile. Il faut dire que nous n'avons encore ajouté ou surchagé aucune classe.
Surcharge et parents
Quand on utilise l'héritage, le but est tout de même de définir de nouvelles méthodes ou d'en améliorer une existente.
Pour cela, il suffit de définir dans la classe enfant une méthode avec un nom déjà existant dans la classe parent.
Dans le cas de notre Carapuce, on voudrais re-définir le constructeur pour le simplifier, puis appeler le constructeur parent avec les données complètes.
<?php
include './pokemon.php';
class Carapuce extends Pokemon
{
public function __construct ($level, $life = null)
{
$name = 'Carapuce';
$max_life = 100 + 5 * $level;
$life = $life ?? $max_life;
$type = 'eau';
$strength = 10 + 2 * $level;
parent::__construct($name, $max_life, $life, $level, $type, $strength);
}
public static function say_hi ()
{
echo "Cara !\n";
}
}
$carapuce = new Carapuce(5);
var_dump($carapuce);Pour cela, on utilise la forme suivante, parent::methode.
Dans ce cadre, on a parfois besoin d'accéder à une méthode ou un attribut d'un parent.
Typiquement, cela est vrai quand on souhaite surcharger le constructeur d'une méthode.
Late Static Binding
Si on lance le code précédent tel quel, on se rend compte que notre pokémon dit "Hi !" au lieu de "Cara !".
On va donc modifier le code de la fonction __construct de la classe Pokemon pour tutiliser static au lieu de self.
Or, self fait référence à la classe dans laquelle il est employé. Comme on fait appel au constructeur du parent, il s'agit Pokemon et non Carapuce.
Ce comportement viens du fait que l'on appel le constructeur parent, qui lui même appel say_hi() via le mot clef self.
Pour résoudre ce problème, on va utiliser la "Résolution statique à la volée" via le mot clef static pour faire plutôt référence à la classe appelée lors de l’exécution.
<?php
class Pokemon
{
...
public function __construct ($name, $max_life, $life, $level, $type, $strength)
{
...
static::say_hi();
}
...
}Et voilà, notre Carapuce dit bien "Cara !" au lieu de "Hi !".
Fonctions privées et héritage
Lors de l'héritage, les attributs et les méthodes privé(e)s ne sont pas transmises à la classe enfant.
Pour cela, PHP offre le mot clef protected, qui s'utilisera comme public et private.
Pourtant, on peut vouloir s'assurer que des méthodes ou des attributs ne soient pas accessibles depuis l'extérieur, tout en les rendant héritables.
Un élément protected a le même comportement qu'un élément private, à la seule différence que lui sera héritable.
Utiliser une interface
En plus du mécanisme de classe, l'OOP introduit la notion d'interface.
En clair, une interface vous dit par exemple qu'un item peut être utilisé, qu'il peut être jeté, etc..
Mais elle ne vous dis pas comment cela va être fait.
Une interface est un moyen d'assurer qu'un ensemble de classes offrira des méthodes cohérentes.
Pour cela, une interface vous permet de définir des signatures de méthodes, mais sans définir leurs corps.
Exemple avec une pokéball
Par exemple, on pourrais créer une interface Usable, qui décrit les objets utilisables.
<?php
interface Usable
{
public function use ($target);
}Tout objet qui implémentera l'interface Usable devra avoir une méthode use($target), sinon PHP lèvera une Fatal Error.
<?php
include_once './usable.php';
class Pokeball implements Usable
{
...
public function use ($pokemon)
{
echo $this->name . ' lancée sur ' . $pokemon->name . '...';
$catch = $this->try_catch($pokemon);
if (!$catch)
{
echo "ko.\n";
return false;
}
echo ' ' . $pokemon->name . ' a été capturé !';
return true;
}
}
Pour utiliser l'interface, on utilisera implements. Ici, on va l'implémenter sur Pokeball.
Utiliser une classe abstraite
En OOP il existe aussi la notion de classe abstraite.
Une classe abstraite est un peu une sorte d'interface, mais qui définirai le corps de certaines méthodes.
Une classe abstraite peu seulement être étendue, pas instanciée.
Les classes abstraites sont un moyen de faire la même chose qu'une interface, mais en définissant certaines méthodes communes. Contrairement aux interface, on peu définir des méthodes privées ou protégées.
Exemple
On va créer une classe abstraite qui définie une méthode normale et une abstraite, puis une classe qui l'étend et définie la méthode abstraite.
<?php
abstract class Truc
{
public function method1 ()
{
echo 'Method1';
}
abstract public function method2 ($arg);
}<?php
class Machin extends Truc
{
public function method2 ()
{
echo 'Method2';
}
}Les méthodes magiques
PHP permet de définir un certain nombre de méthodes "magiques", qui seront appelée automatiquement par PHP lors de certains évènements.
Elles commencent toutes par __. On en a déjà vu une, __construct.
Il existe les méthodes magiques suivantes : __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state() __clone() et __debugInfo()
Prise en main

Créer des pokémons
Créer deux nouvelles classes pour implémenter les starter manquant, Bulbizarre et Salamèche.



Faîtes en sorte que l'on ne puisse pas créer un objet Pokemon.
Améliorer les pokéball
Modifier la classe Pokeball pour la renommer Ball.
Créez des classes pour les différentes balls : Pokeball, Superball, Hyperball et Masterball.



Créez une potion
Créez une classe pour représenter les potions (qui sont des items).
Créez des classes pour les différentes potions : Potion, Superpotion, Hyperpotion et Potionmax.

Make a bot
Créez un bot cherchant à capturer un pokémon lors d'un combat.
Dans ce combat, vous disposez de 3 pokéballs, de 2 potions et d'une super potion.

Vous avez un Carapuce niveau 5 et vous combattez un Salameche niveau 8.
Salameche attaque à chaque tour.
Formules
Potions : Elles rendent, 20, 50, 200 et 100% des PV.
Balls : Elles sont lvl 10, 30 et 50. La masterball capture à tout les coups.
Pokemons :
- Carapuce : 9 de vie par lvl et 2 d'attaque.
- Salameche : 5 de vie par lvl et 4 d'attaque.
- Bulbizarre : 7 de vie par lvl et 3 d'attaque


PHP Orienté Objet
By plebweb
PHP Orienté Objet
- 856