web-dev-qa-db-fra.com

Puis-je inclure du code dans un PHP classe?

Je veux faire une classe PHP, disons Myclass.php. Maintenant, dans cette classe, je veux définir uniquement la classe elle-même et certaines variables d'instance. Mais toutes les méthodes doivent provenir d'un fichier Myclass_methods.php. Puis-je simplement inclure ce fichier dans le corps de la classe?

J'ai de bonnes raisons pour lesquelles je veux séparer cela. En bref, j'aurai un back-end dans lequel je pourrai changer la logique métier d'une classe, tout en restant inchangé. Le système gère tous les ORM et autres éléments pour moi.

Toutefois, s'il s'agit d'une mauvaise idée, il peut être préférable de générer à nouveau le fichier de classe complet après avoir modifié la logique métier (par conséquent, les méthodes définies par l'utilisateur dans ce cas).

Question sur les performances: si, lors d’une demande, Myclass.php est inclus une seule fois, c’est en fait que Myclass_methods.php doit également être inclus une seule fois. Peut-être tort. Experts?

53
openfrog

Non. Vous ne pouvez pas inclure de fichiers dans le corps de la classe.
Dans un fichier définissant une classe, vous ne pouvez inclure des fichiers que dans un corps method ou en dehors du corps de la classe.

De votre description je prends vous voulez ceci:

<?php // MyClass.php
class MyClass
{
    protected $_prop;
    include 'myclass-methods.php';
}

<?php // myclass-methods.php
public function myMethod()
{
   $this->$_prop = 1;
}

L'exécution de ce code entraînera

Parse error: syntax error, unexpected T_INCLUDE, expecting T_FUNCTION

Qu'est-ce qui est possible cependant?

<?php // MyClass.php
class MyClass
{
    protected $_prop;
    public function __construct() // or any other method
    {
        include 'some-functions.php';
        foo($b); // echoes 'a';
    }
}

<?php // some-functions.php
$b = 'a';
function foo($str)
{
   echo $str;
}

En procédant de cette façon, le contenu du fichier include sera importé dans la portée de la méthode, pas dans la classe. Vous pouvez inclure des fonctions et des variables dans le fichier à inclure, mais pas des méthodes. Vous pourriez mais ne devriez pas y insérer également des scripts entiers et changer le fonctionnement de la méthode, par exemple.

<?php // MyClass.php
    // ...
    public function __construct($someCondition)
    {
        // No No Code here
        include ($someCondition === 'whatever') ? 'whatever.php' : 'default.php';
    }
    // ...

<?php // whatever.php
    echo 'whatever';

<?php // default.php
    echo 'foo';

Toutefois, appliquer des correctifs à la classe de cette manière pour présenter un comportement différent n’est pas la bonne façon de le faire en POO. C'est tout simplement faux et devrait faire saigner vos yeux.

Etant donné que vous souhaitez modifier le comportement de manière dynamique, l'extension de la classe n'est également pas une bonne option (voir ci-dessous pourquoi). Ce que vous voudrez vraiment faire est d'écrire un interface et faire que votre classe utilise des objets implémentant cette interface, s'assurant ainsi que les méthodes appropriées sont disponibles. Ceci s'appelle un modèle de stratégie } et fonctionne comme ceci:

<?php // Meowing.php 
interface Meowing
{
    public function meow();
}

Vous avez maintenant le contrat auquel tous les comportements de miaulement doivent obéir, à savoir la méthode du miaou. Définissez ensuite un comportement de Meowing:

<?php // RegularMeow.php
class RegularMeow implements Meowing
{
    public function meow()
    {
        return 'meow';
    }
}

Maintenant pour l'utiliser, utilisez: 

<?php // Cat.php
class Cat
{
    protected $_meowing;

    public function setMeowing(Meowing $meowing)
    {
        $this->_meowing = $meowing;
    }

    public function meow()
    {
        $this->_meowing->meow()
    }
}

En ajoutant le paramètre Meowing TypeHint à setMeowing, vous vous assurez que le paramètre transmis implémente l'interface Meowing. Définissons un autre comportement de Meowing:

<?php // LolkatMeow.php
class LolkatMeow implements Meowing
{
    public function meow()
    {
        return 'lolz xD';
    }
}

Maintenant, vous pouvez facilement échanger des comportements comme ceci:

<?php
require_once 'Meowing.php';
require_once 'RegularMeow.php';
require_once 'LolkatMeow.php';
require_once 'Cat.php';

$cat = new Cat;
$cat->setMeowing(new RegularMeow);
echo $cat->meow; // outputs 'meow';
// now to change the behavior
$cat->setMeowing(new LolkatMeow);
echo $cat->meow; // outputs 'lolz xD';

Vous auriez également pu résoudre le problème ci-dessus avec héritage en définissant un résumé BaseCat et une méthode meow, puis en dérivant des classes régulières RegularCat et Lolkat, . Si vos chats ne changent jamais la façon dont ils miaulent, utilisez l’héritage, mais si votre RegularCat et votre Lolkat sont supposés être capables de miauler de façon arbitraire, utilisez le modèle Strategy.

Pour plus de modèles de conception } en PHP, vérifiez les ressources suivantes:

163
Gordon

Ne pensez-vous pas qu'il serait judicieux de créer la classe de base avec les fonctionnalités de base pertinentes, puis de l'étendre avec les méthodes requises - cela semble être une approche plus logique.

8
John Parker

Je vais commencer par dire que je ne comprends pas trop pourquoi ce problème n'est pas mieux résolu en utilisant une classe de base contenant les méthodes, des sous-classes contenant les données et le chargement dynamique de classes. Je suppose que vous avez une bonne raison.

Une fois que votre fournisseur prend en charge PHP 5.4, vous pouvez faire ce que vous voulez en utilisant des traits.

Fichier de code:

if ($pet === 'dog') include 'dog.php';
elseif ($pet === 'cat') include 'cat.php';
else die('Unknown pet');

class Pet {
  use PetSounds;
}

$myPet = new Pet();
$myPet->speak();

Fichier cat.php

trait PetSounds {
  function speak() { echo 'meow'; }
}

Fichier dog.php

trait PetSounds {
  function speak() { echo 'woof'; }
}

Vous pouvez rendre cela encore plus propre en nommant les deux fichiers include de la même manière, en les plaçant dans des sous-répertoires différents et en utilisant set_include_path () ou en définissant une fonction __autoload () pour les sélectionner. Comme je l'ai dit cependant, ce même problème pourrait être résolu mieux en utilisant l'héritage. Toutefois, si vous rencontrez un problème de type héritage multiple, si, par exemple, vous avez quatre types d'animaux de compagnie avec cinq types de couleurs et trois types de poils et si vous avez besoin d'une combinaison différente de méthodes pour chacune des 60 classes différentes, c'est la bonne solution. .

La version 5.4 n’est actuellement qu’une version candidate (au 24/02/2012) et, même une fois publiée, la plupart des hôtes ne la prendront pas en charge pendant plusieurs mois - la mienne a pris 18 mois après la publication de la version 5.3 avant de l’appuyer. Jusque-là, vous devez écrire des fichiers de classe complètement séparés et complets. Vous pouvez toutefois formater vos classes avec un éventuel changement de traits en tête.

À l’heure actuelle, vous pouvez obtenir partiellement ce que vous voulez en utilisant des méthodes magiques et avoir une mise à niveau facile des traits quand ils sont disponibles.

Fichier de code:

if ($pet === 'dog') include 'dog.php';
elseif ($pet === 'cat') include 'cat.php';
else die('Unknown pet');

class Pet {
  public function __call($name, array $arguments)
  {
    array_unshift($arguments, $this);
    return call_user_func_array("TraitFunc_$name", $arguments);
  }
}

$myPet = new Pet();
$myPet->speak();

Fichier cat.php

function TraitFunc_speak(Pet $that) { echo 'meow'; }

Fichier dog.php

function TraitFunc_speak(Pet $that) { echo 'woof'; }

Vous êtes toutefois limité en ce que vos fonctions ne peuvent pas accéder aux propriétés et méthodes des classes privées et protégées et vous ne pouvez pas utiliser cette méthode pour fournir des méthodes magiques telles que __get (). Les traits vont résoudre ces deux limitations.

6
ldrut

Qu'en est-il d'utiliser des traits pour cela? Serait-ce une option acceptable? C’est quelque chose que j’expérimente actuellement et qui semble fonctionner assez longtemps. 

Voici une version simplifiée de ce que je fais. J'ai une application avec des fichiers de base partagés et plusieurs projets. Au sein de ces projets, j'ai des modules. Je souhaite disposer de fonctions disponibles pour l'ensemble du projet à un niveau de base, mais uniquement pour ce projet spécifique.

Mon contrôleur de projet  

if(is_file(PROJECT_PATH.'/project_extensions.trait.php')){
  // additional functions for this specific project
  require_once(PROJECT_PATH.'/project_extensions.trait.php');
}else{
  // no additional functions
  trait Extensions{};
}


Class Project{
  USE Extensions;

  // default functions shared between all projects
  function shared_stuff(){

  }
}

Fichier d'extensions

trait Extensions{
  // project-specific extensions
  function this_project_only(){
    echo 'Project Only';
  }
}

Fichier de module dans le projet

class MyModule extends Modules{ // modules extends projects in a different class not relevant here

  function do_something(){
    echo $this->project_only();
  }
}
5
Thomas Smart

On fait revivre une vieille question mais c'est une solution assez simple. Avez-vous besoin que les appels de fonctions communs soient exclusifs à votre classe? Sinon, incluez simplement vos fichiers de fonctions communes dans la même étendue que votre classe. Vous devrez créer des méthodes dans votre classe, mais elles n’auront besoin que d’appeler la fonction commune. Voici un exemple de serveur SOAP simple:

<?php

include 'post_function.php';

$server = new SoapServer( null, array('uri' => "http://localhost/") );
$server->setClass(  'postsoapclass' );
$server->handle();


class postsoapclass
{
    public function animalNoise( $animal )
    {
        return get_animal_noise($animal);
    }
}

?>

post_function.php 

<?php

function get_animal_noise($animal)
{
    if(strtolower(trim($animal)) == 'pig')
    {
        return 'Oink';
    }
    else 
    {
        return 'This animal is mute';
    }
}

?>
0
Chris Ohmstede

J'ai dû faire ce que vous décrivez dans les cas où je maintiens une version gratuite et une version premium du même logiciel. Comme @Gordon l'a noté, vous ne pouvez pas faire exactement cela: 

class SomeClass {  

  premium_file = "premium.php";
  if (file_exists($premium_file)) {
    require($premium_file);
  }

Au lieu de cela, je fais ceci: 

  premium_file = "premium.php";
  if (file_exists($premium_file)) {
    require($premium_file);
  }

  class SomeClass {
    ...

Pour les fonctions que vous souhaitez référencer, créez des méthodes de classe dans la classe principale et appelez la méthode du fichier inclus en passant le pointeur $this en tant que paramètre. Pour que je sache en un coup d'œil où sont les fonctions, je préfixerai le nom des fonctions incluses, comme indiqué ci-dessous: 

  class SomeClass {
    ... 
    // Premium functions
    public function showlist() {
      premium_showlist($this);
    }
0
Scott C Wilson

Depuis (PHP5.4} version vous pouvez créer objets dynamiques comme ceci: https://github.com/ptrofimov/jslikeobject

Mais c’est la à peine la meilleure pratique.

0
Renato Cuccinotto

Je suis récemment tombé sur cette idée et j'ai trouvé une solution qui m'a aidé dans mon cas. Je voulais beaucoup de fonctions dans une classe, mais la classe est devenue trop lourde, donc je voulais séparer les fonctions de la classe en groupes pour plus de lisibilité. Cela a pris un peu de temps, mais comme les fonctions de la classe ne reposaient pas (beaucoup) sur $ this, j'ai supprimé "$ this" des fonctions de la classe et créé plusieurs fichiers utilitaires pour inclure ces fonctions. Lorsque $ this était nécessaire, je pouvais néanmoins déplacer la fonction dans un fichier d'aide, en passant $ this à la fonction, en ajoutant des fonctions set/get publiques si nécessaire. C'est un hack, mais c'est sûr d'aider quelqu'un

` classe myClass { var x;

    function myClass()
    {
        $this->x = 0;
    }

    function myFunc1Group1()
    {
        $x = $this->x;
        $x++;
        $this->x = $x;
    }
    function myFunc2Group1(){}

    function myFunc1Group2(){}
    function myFunc2Group2(){}
}

`

peut être travaillé autour de

` classe myClass { var x;

    function myClass()
    {
        $this->x = 0;
    }

    function doSomething()
    {
        // not called on $this but takes $this as a parameter
        myFunc1Group1($this);
    }
}

`

et ensemble de fonctions d'assistance 1

function myFunc1Group1($THIS_OBJECT) { $x = $THIS_OBJECT->getX(); $x++; $THIS_OBJECT->setX($x); } function myFunc2Group1($THIS_OBJECT){}

et jeu de fonctions d'assistance 2, etc.

Probablement pas le meilleur itinéraire dans tous les cas, mais m'a beaucoup aidé. Fondamentalement, les fonctions de classe devaient uniquement être construites et déléguées, et les calculs étaient placés dans des aides.

0
naglma