php[tek] 2018 : Call for Speakers

Typage objet

PHP 5 introduit le typage explicite (littéralement, Type Hinting, allusion à un type). Les fonctions peuvent maintenant imposer aux paramètres un typage (en spécifiant le nom de la classe dans le prototype de la fonction), et ainsi d'être des Objets, des interfaces, des tableaux (depuis PHP 5.1), ou de type callable (depuis PHP 5.4). Cependant, si NULL est utilisé en tant que valeur par défaut du paramètre, il sera autorisé comme argument pour tous les futurs appels.

Si une classe ou une interface est spécifiée comme type, alors toutes les classes en héritant ou ses implémentations seront aussi autorisées.

La spécification d'un type ne peut être utilisée avec les types scalaires comme int ou string. Les ressources et les Traits ne sont pas non plus autorisés.

Exemple #1 Exemples de typage d'objets

<?php
// Un exemple de classe
class MaClasse
{
    
/**
     * Fonction de test
     *
     * Le premier paramètre doit être un objet de type AutreClasse
     */
    
public function test(AutreClasse $autreclasse) {
        echo 
$autreclasse->var;
    }


    
/**
    * Une autre fonction de test
    *
    * Le premier paramètre doit être un tableau
    */
    
public function test_array(array $input_array) {
        
print_r($input_array);
    }
    
    
/**
     * Le premier paramètre doit être un itérateur
     */
    
public function test_interface(Traversable $iterator) {
        echo 
get_class($iterator);
    }
    
    
/**
     * Le premier paramètre doit être une fonction de rappel (callable)
     */
    
public function test_callable(callable $callback$data) {
        
call_user_func($callback$data);
    }
}

// Un autre exemple de classe
class AutreClasse {
    public 
$var 'Bonjour le monde!';
}
?>

Si un paramètre ne satisfait pas les conditions imposées, une erreur fatale (qui peut être attrapée) est émise.

<?php
// Une instance de chaque classe
$maclasse = new MaClasse;
$autreclasse = new AutreClasse;

// Erreur fatale : Argument 1 doit être un objet de la classe AutreClasse
$maclasse->test('salut');

// Erreur fatale : Argument 1 doit être une instance de AutreClasse
$foo = new stdClass;
$maclasse->test($foo);

// Erreur fatale : Argument 1 ne doit pas être null
$maclasse->test(null);

// Fonctionne : Affiche 'Bonjour le monde!'
$maclasse->test($autreclasse);

// Erreur fatale : Argument 1 doit être un tableau
$maclasse->test_array('a string');

// Fonctionne : Affiche le tableau
$maclasse->test_array(array('a''b''c'));

// Fonctionne : Affiche ArrayObject
$maclasse->test_interface(new ArrayObject(array()));

// Fonctionne : Affiche int(1)
$maclasse->test_callable('var_dump'1);
?>

Le typage fonctionne aussi avec les fonctions :

<?php
// Un exemple de classe
class MaClasse {
    public 
$var 'Bonjour le monde!';
}

/**
 * Fonction de test
 *
 * Le premier paramètre doit être un objet de type MaClasse
 */
function maFonction(MaClasse $foo) {
    echo 
$foo->var;
}

// Fonctionne
$maclasse = new MaClasse;
maFonction($maclasse);
?>

Le typage objet autorise la valeur NULL :

<?php

/* On accepte la valeur NULL */
function test(stdClass $obj NULL) {

}

test(NULL);
test(new stdClass);

?>
add a note add a note

User Contributed Notes 20 notes

up
65
Daniel dot L dot Wood at Gmail dot Com
9 years ago
People often ask about scalar/basic typehints.  Here is a drop in class that I use in my MVC framework that will enable typehints through the use of a custom error handler.

Note: You should include this code above all other code in your include headers and if you are the using set_error_handler() function you should be aware that this uses it as well.  You may need to chain your set_error_handlers()

Why?
1) Because people are sick of using the is_* functions to validate parameters.
2) Reduction of redundant coding for defensive coders.
3) Functions/Methods are self defining/documenting as to required input.

Also..
Follow the discussion for typehints in PHP 6.0 on the PHP Internals boards.

<?php

define
('TYPEHINT_PCRE'              ,'/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/');

class
Typehint
{

    private static
$Typehints = array(
       
'boolean'   => 'is_bool',
       
'integer'   => 'is_int',
       
'float'     => 'is_float',
       
'string'    => 'is_string',
       
'resrouce'  => 'is_resource'
   
);

    private function
__Constrct() {}

    public static function
initializeHandler()
    {

       
set_error_handler('Typehint::handleTypehint');

        return
TRUE;
    }

    private static function
getTypehintedArgument($ThBackTrace, $ThFunction, $ThArgIndex, &$ThArgValue)
    {

        foreach (
$ThBackTrace as $ThTrace)
        {

           
// Match the function; Note we could do more defensive error checking.
           
if (isset($ThTrace['function']) && $ThTrace['function'] == $ThFunction)
            {

               
$ThArgValue = $ThTrace['args'][$ThArgIndex - 1];

                return
TRUE;
            }
        }

        return
FALSE;
    }

    public static function
handleTypehint($ErrLevel, $ErrMessage)
    {

        if (
$ErrLevel == E_RECOVERABLE_ERROR)
        {

            if (
preg_match(TYPEHINT_PCRE, $ErrMessage, $ErrMatches))
            {

                list(
$ErrMatch, $ThArgIndex, $ThClass, $ThFunction, $ThHint, $ThType) = $ErrMatches;

                if (isset(
self::$Typehints[$ThHint]))
                {

                   
$ThBacktrace = debug_backtrace();
                   
$ThArgValue  = NULL;

                    if (
self::getTypehintedArgument($ThBacktrace, $ThFunction, $ThArgIndex, $ThArgValue))
                    {

                        if (
call_user_func(self::$Typehints[$ThHint], $ThArgValue))
                        {

                            return
TRUE;
                        }
                    }
                }
            }
        }

        return
FALSE;
    }
}

Typehint::initializeHandler();

?>

An are some examples of the class in use:

<?php

function teststring(string $string) { echo $string; }
function
testinteger(integer $integer) { echo $integer; }
function
testfloat(float $float) { echo $float; }

// This will work for class methods as well.

?>

You get the picture..
up
3
MiChAeLoKGB
10 months ago
Please note, that with PHP 7, type hinting is faster than normal implementation.
This has to do with type hinting before PHP 7 allowing only objects and arrays and anything else (scalars) would throw an error.

I did some using code "doom at doom dot pl" posted 2 years ago

Here are results from couple of test runs using PHP 7 (200.000 loops)
- teststringNormal took: 0.01799488067627
- teststringOverhead took: 0.012195825576782

- teststringNormal took: 0.027030944824219
- teststringOverhead took: 0.012197017669678

- teststringNormal took: 0.017856121063232
- teststringOverhead took: 0.012274980545044

As you can see, the overhead is faster and much more consistent.

Here is one run with the is_string check removed from Normal:
- teststringNormal took: 0.010342836380005
- teststringOverhead took: 0.012849092483521
up
33
michaelrfairhurst at gmail dot com
4 years ago
The scalar type hinting solutions are all overthinking it. I provided the optimized regex version, as well as the fastest implementation I've come up with, which just uses strpos. Then I benchmark both against the TypeHint class.

<?php
function optimized_strpos($ErrLevel, $ErrMessage) {
        if (
$ErrLevel == E_RECOVERABLE_ERROR
           
// order this according to what your app uses most
           
return strpos($ErrMessage, 'must be an instance of string, string')
                ||
strpos($ErrMessage, 'must be an instance of integer, integer')
                ||
strpos($ErrMessage, 'must be an instance of float, double')
                ||
strpos($ErrMessage, 'must be an instance of boolean, boolean')
                ||
strpos($ErrMessage, 'must be an instance of resource, resource');
}

function
optimized_regex($ErrLevel, $ErrMessage) {
        if (
$ErrLevel == E_RECOVERABLE_ERROR) {
            if(
preg_match('/^Argument \d+ passed to (?:\w+::)?\w+\(\) must be an instance of (\w+), (\w+) given/', $ErrMessage, $matches))
                return
$matches[1] == ($matches[2] == 'double' ? 'float' : $matches[2]);
        }
}
?>

BENCHMARKING Typehint::handleTypehint()
string type hint.....[function it(string $var) {}]............2.1588530540466 seconds
float type hint......[function it(float $var) {}].............2.1563150882721 seconds
integer type hint....[function it(integer $var) {}]...........2.1579530239105 seconds
boolean type hint....[function it(boolean $var) {}]...........2.1590459346771 seconds

BENCHMARKING optimized_regex()
string type hint.....[function it(string $var) {}]............0.88872504234314 seconds
float type hint......[function it(float $var) {}].............0.88528990745544 seconds
integer type hint....[function it(integer $var) {}]...........0.89038777351379 seconds
boolean type hint....[function it(boolean $var) {}]...........0.89061188697815 seconds

BENCHMARKING optimized_strpos()
string type hint.....[function it(string $var) {}]............0.52635812759399 seconds
float type hint......[function it(float $var) {}].............0.74228310585022 seconds
integer type hint....[function it(integer $var) {}]...........0.63721108436584 seconds
boolean type hint....[function it(boolean $var) {}]...........0.8429491519928 seconds
up
4
jesdisciple @t gmail -dot- com
10 years ago
The manual's sample code says:
<?php
//...
// Fatal Error: Argument 1 must not be null
$myclass->test(null);
//...
?>

And this is true, unless a default value of NULL is given; in fact, this is the only way to give a default value for object arguments (as a default value must be a constant expression):
<?php
$mine
= new MyClass();
$mine->test(NULL);
class
MyClass{
    public function
__construct(OtherClass $arg = NULL){
        if(
is_null($arg)){
           
//Apply default value here.
       
}
    }
    public function
test(array $arr = NULL){
       
print_r($arr);
    }
}
class
OtherClass{
   
}
?>
up
3
doom at doom dot pl
3 years ago
I've done some tests of the overhead that class Typehint  gives us.
At my PC it goes as follows:
teststringNormal took: 0.041965961456299
teststringOverhead took: 0.48374915122986
It's like 10x longer time (not mention about memory usage), it's just because exception is thrown EVERY SINGLE TIME, along with expensive preg_match() and debug_backtrace() calls.
I think that using class in bigger applications will increase overhead like 100% or more.
<?php

function teststringOverhead(string $string) {
    return
$string;
}
function
teststringNormal($string){
    if(!
is_string($string)){
        return;
    }
    return
$string;
}
$loopTimes = 20000;

/////////// test of overhead implementation vs normal
$t1 = microtime(true);
for(
$i = 0; $i <= $loopTimes; $i++)  teststringNormal("xxx");
echo
"<br>teststringNormal took: " . (microtime(true) - $t1);

$t2 = microtime(true);
for(
$i = 0; $i <= $loopTimes; $i++)  teststringOverhead("xxx");
echo
"<br>teststringOverhead took: " . (microtime(true) - $t2);
?>
up
8
bantam at banime dot com
8 years ago
Daniel's typehint implementation was just what I was looking for but performance in production wasn't going to cut it. Calling a backtrace every time hurts performance. For my implementation I didn't use it, after all, PHP tells us what the data type is in the error message, I don't feel I need to evaluate the argument where I am using typehinting. Here is the cut down version I use in my error handling class:

<?php
       
public static function typehint($level, $message)
        {
            if(
$level == E_RECOVERABLE_ERROR)
            {
                if(
preg_match('/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/', $message, $match))
                {
                    if(
$match[4] == $match[5])
                        return
true;
                }
            }
           
            return
false;
        }
?> }
    }
 ees to veust thatbodydiv>
up
bantam at banime dot com ¶
9 yearsars4
I've done some tests of the "de% li/div> e ation `br />At mye u-C tes,ion ws fap, type hi appliit_errobst reyw cla the At mye ua thens are sp; &s . Forc typn with the is_string r /isn't going-<?php

function teststringAt mye u">($level(($string(//Apply default value here.function optimized_regexAt mye u">($level(Typehint(array MyClass; }
function
$i (array 0; $i (array $i ;

$i++)  ($level);
}

function E_RECOVERABLE_ERROR)
     {$float(array $level);
}

functilass="default">$t2 );
MyClass; }
function
$i (array 0; $i (array $i ;

$i++)  ($level);
}

function E_RECOVERABLE_ERROR)
     {$float);
$level);
}

functilass="de>?>
<?ingN8846044ype 053><?in2163460254669rmal took: 0.Rruns k: 0.041/>At mye u()ingt/>Here
T1/>At mye u()mal took: 0.e aboee:ut down versa thens are t dos (div> sugge/>Herby ation )inge hi apeobs>
2033
up
bantam at banime dot com
4 years4-25 03:05ng>
I've done some tests of the over it, afteHers pesass="defaultere nsur typehintiwas jusefault"><?php
function function t">$floatfunction $floatpublic static function 0; $floatpublic static function 0);

$floatpublic static function 0);
$floatpublic static function 0);
$floatpublic static function 0] == $floatpublic static function 0])
    &nbsl
$floatpublic static function 0])
    &nbsl
lass="comment">// This will work for class **eyword * @long s Invali="keywordsNGLE TIMeyword */"yword">function
optimized_regex) = public static function 0();
           l>is_string
public static function 0[5] == ('args'][public static function 0();
           ="default">$ThBackTrace
public static function $ThTracepublic static function $ThTrace))
     ;       if(
$ThTrace
public static function 0($ErrLevel ($arg))
     p;       if(
}
$ThBackTrace [5][$float][initializeHandler][is_string($level))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace: $ThTrace[5] == ('args'][$ThTrace);
}

functi                             }
     bdefk              =type">$float][initializeHandler][is_string($string))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace: $ThTrace[5] == ('args'][$ThTrace)
                                  }
     bdefk              =type">$float][initial/span>][is_string($string))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace:
$ThTrace[5] == ('args'][$ThTrace)
                                  }
     bdefk              =type">$float][initial/span>][is_string(true))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace:
$ThTrace[5] == ('args'][$ThTrace)
                                  }
     bdefk              =type">$float][initial/span>][is_string(float $float))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace:
$ThTrace[5] == ('args'][$ThTrace)
                                  }
     bdefk              =type">$float][initial/span>][is_string(float $float))
     p;       if(
$ThTracepublic static function 0: $ThTracepublic static function $ThTrace:
$ThTrace[5] == ('args'][$ThTrace);
}

functi                             }
     bdefk                    ul lt">is_string($arg)
     p;       if(
$ErrLevel (float $float))
     p;       if(
$ThTracepublic static function 0: $ThTrace<">(float $float))
      .t">$ThTrace
);
}

functi              } elype       if($ThTracepublic static function 0: $ThTrace) = float $float))
      .t">$ThTrace
);
}

functi              }             long fault">MyClass($arg)
                     class="default"class="default">optimized_regex
) = float $float] == ($match);
$match);
is_string) = $float][initial/span>][$match][initializeHandler][$match][initializeHandler][$matchpublic static function MyClass(float $float);
}

function $match
) = float $float] == ($match('must be an instance of resoHelloe );
}

function >);
up
doom at doom dot plnetatfunka class="genanchor" href="#111411"> ¶
8 yearsars8 08:36ng>
Daniel's typehint implementation ,.

<?php

typehint) = ) = float $floatAt myd">, float $float);
}

functio) clbr />
false][?> &nhelps,ess a --Dmilasyiv>
up
doom at doom dot plmorpmanc class="genanchor" href="#111411"> ¶
8 ye5-07ago
2
The manual's sample code sayolean $van cfunks) and .0.6rfmans="ke. In o an .0.6rfmanefneeas="defaulttitlmeter,gNefauleault val\(\) mu
up
doom at doom dot plignodef/>&nnetweblogass class="genanchor" href="#88015"> ¶
9 years7-14 06:18ng>
The manual's sample code saLovee effnet!default"><?Howe. Fon ian 100%long s c) caabld f not > <?php
       e>);
}

functian>typehint) = float $float, $message)
     p;          ullt">$ErrLevel
public static function E_RECOVERABLE_ERROR)
     p;       if(
$ErrLevel public static function false][$message)
     lt">$message
= false)
     lt">$message
($message($message) = $message) = $message)
     plt">0
][false][initializeHandler= 5) = $ThTracepublic static function $matchpublic static function $ThTracepublic static function $match){
                if(
$ErrLevel ][initializeHandlerpublic static function false
($message) = $message($messagepublic static function $ErrLevel public static function false][initializeHandler= 5) = $messagepublic static function false][             ;      
         ;      
         long fault">MyClass
$arg)
            if(
false][?> }
);
9208
up
doom at doom dot pl
4 ye6rs4-19 01:31ong>
I've done some tests of the n;degards="kame"> http://"ht.net4559&en/mp;vote=up" title="Vote uguag#103729="20 ,rwhen the cutypein Eclips ,.<?Tn mmarmahats /* @vivcevwriabldeOlt vaN"><?/* @vivcevwriablde\Some\N">
7907
div> doom at doom dot pl
4 ye5-08-30 01:52ong>
2
I've done some tests of theibr erkr />&n>&nprettome ossefault"><?$lwors=hhb_r, (be('efaul',file("foofile")on <?$socket=hhb_r, (be('Socket',socket_crenge(AF_INET,SOCK_STREAM,getprotobya"><('tcp'))on <?$size=hhb_r, (be('...d,filesize(thatfile)on <?    ="defaulthhb_r, (be(/*class=*/l do,/*mixed*/lvwriabld)       if(}
up
doom at doom dot pl
4 ye0-e0-22 06:02ong>
7
I've done some tests of the "havI 15d+)a litongcbea;<?Firgt/>ethod):rby ts>}
optimized_regex(array $ThBackTrace )
     p;     ul lt">is_string
($string)
     ;;       if(
$ThTrace ($stringclass=, o );
}

functi)            default">false)
     n
?> ethod):rby ult valrep="ke versanativepe, I efault">}
optimized_regex(float $float)
     p;     default">false
)
     n
?> ethod):rby > }
optimized_regex) = $level)
     p;     default">false
)
     n
?> <?Asr_hanbsp;se ,.ethod)by ult valis.<><?nor _hanknowdiv&v>
up
doom at doom dot pl
4 ye1rs4-29 06:33
6
I've done some tests of theib veueclips ganymed+)as an IDE"Nefaitpanfev c"...ellike s>e tr <?php
       e>);
}

functi> ?> );
}

functi{default"><?pan>typehint(array $ThBackTrace public static function false($match){
     );       if(false
(<?pan>false();
     {    }default}default"><?">false
($match($string) = false($match(initial/span>(array $ThBackTrace (false(();
       ">false
false  afhersing hur)Card(), dueterein me, I wn vers,ueclips anfev cmrde<?ic tuefa />&nvee epraefaing uefainclud muin mCard()s="defaultun mymyod+)l aplnges=thenewn>_hanw100%fiefa />&nj, (\Norg tyy\Noriate ;o)default"><?p.s. te /div:rwhen th mu is_stdiv>
up
doom at doom dot pl
9 yearsars8 05:59ng>
The manual's sample code sayommal tou"> on mymoriginng po (\ ealcktr and <?Kalkamaringeabsolu.elymyorrec ,wihats s touNefais a h"ke.  Evee ead whon veswihaNefaannts="kasebr />&nooleaTypsyntaxanativepneeds="kapo (\on in m'"ht i0.6rnngs'\ evelopafte apread;un support.fault"><?Tbr ks,ess a ess a ati">is_stdiv>
up
doom at doom dot pl
9 yeare0-24 08:26ng>
The manual's sample code saOad xcefulbr ertr and yoleaH $van clorformc/uldrnclafiefaun in mdocuafteatiIMn(on wes>&n>&nanvee excefulbrool .feyhanlrI tryan clomyod+)lo I0.6rfmans ra eansr_har las6rnngclass= bsp;p="ke vtitself i0.o.ethod)Norlt doNori(\ it, aftes.
up
doom at doom dot pl
9 yeare0-s8 06:30ng>
I've done some tests of the "fiefaut ra ethss=, sore tuefaabwaytartuefait.default"><?php
       e>);
}

functiabsormae pan>false);
}

functi;      prot vaedt">false
)
           prot vaedt">false
(false) = <?pan>typehint(<?pan>false(public static function $match) = false(public static function $match(<?pan>typehint)
     t)"><?pan>false
()
           }default    "><?pan>typehint) = <?pan>false() = <?pan>typehint(<?pan>false(<?pan>falsepublic static function false);
}

functi;      pn>typehint
(falsepublic static function $match);
<?pan>);(initial/span>(false(public static function $match) = false(public static function $match)
               ">false
(public static function $match<?pan>falsepublic static function false);
}

functi;    pn>typehint
(falsepublic static function $match);
$messagepublic static function $match);
<?pan>false(initial/span>(false(public static function $match) = false(public static function $match)
           ">false
(public static function $match(<?pan>falsepublic static function false);
}

functi;      pn>typehint
(falsepublic static function $match);
<?pan>);(initial/span>(false(public static function $match)
           }default}default">)
  &nbodeme.."a//etc etc longugd false
false  then lngerormcpando <?php
       e>);
}

functifinngclass= ">false
$match<">)
           pn>$match
)
           pn>$match
$match$match))
           pn>$match
($match)
           pn>$match
)
                 pn>typehint
(false<">)
     lt">$message
)
     lt">$message
public static function $match) = $messagepublic static function $match) = $matchpublic static function $match($matchpublic static function $match) = $messagepublic static function $match(<?pan>);(public static function $match<">)
            if(
false
(public static function $match)
            if(
false
(public static function $match)
            if(
false
(public static function $match)
            if(
false
(public static function $match)
            if(
false
(public static function $match)
            if(
false
(public static function $match)
           }default}defaultdefault">)
  &nbodeme.."a//ndivvdefault"><?p>)
     lass= ">false
typehint)
     t">false
)
     )"><?pan>$ErrLevel
)
     -pyyr >);public static function false)
     )"><?pan>false
false)
  &nbclass="ke>)
up
doom at doom dot pljuno class="genanchor" href="#88015"> ¶
9 yeare0-s1 06:20ng>
The manual's sample code sayYPE-HINTING NefaVISIBILITYfault"><?Tle=-wn versa>&nj, (\ead more sma00%pie/span>typelorfoprot vas har ult vasrwhen visibilitomean/di.fault"><?php
     fault"><?p>)
     lass= ">falsepublic static function $match($message)
      fault"><?  pn>typehint
(falsepublic static function $match$messagepublic static function $matchfalse(public static function $match)
      fault"    ">false
(public static function $match)
      fault"  n><?lass= ">false
public static function false= <?  pn>typehint(falsefalsefalse((0<?">false= MyClass(false);
$message);
<?">falsepublic static function MyClass(false((false);
<?">false((MyClass(false);
$message);
<?">false((MyClass(false);
$message);
<?">false((MyClass)
     ()) if(
false <?">false&nn>e tr <,>typelong s an >
up
doom at doom dot pl
4 ye0-e0-22 11:55ong>
7
The manual's sample code saFbretype5.3nveesTIMnNefaa"><?php
     fault"><?p>)
     ="default">typehint) = 0$message)
     lt">$message
)
     lt">$message
public static function $message (0public static function $messagepublic static function 0public static function false<?         if(falsepublic static function $match(?; &nwn vp/di.+),>(?; &nkeywop/di.+)ckeywo/i'n">public static function <?            ulltt">$message) = 0$message)
     lt">$message
public static function falsepublic static function $match) = $matchpublic static function }
$matchpublic static function $match)
     tt">$message
xpl mae>($message);
}

functilt">$message) = $matchpublic static function false<? }
}
$match$messagepublic static function 0public static function falsepublic static function <? }
}
falsepublic static function <?">false) = 0public static function <?">false<?p>)
     ="default">typehint
(array typehint= $message) = 0= <?">false(array typehintpublic static function false<?">false
eefaul>ef="#1>eefaul>