PHP
call_user_method_array> <ctype_xdigit
Last updated: Mon, 12 Sep 2005

XII. Classes/Objects 类/对象函数

简介

本类函数允许获取类和对象实例的信息。可以取得对象所属的类的名字,以及它的成员属性和方法。通过使用这些函数,不仅可以弄清楚一个对象类的全体成员,而且可以知道它的起源(例如,该对象类是哪个类的扩展)。

需求

要编译本扩展模块不需要外部库文件。

安装

本函数库作为 PHP 内核的一部分,不用安装就能使用。

运行时配置

本扩展模块在 php.ini 中未定义任何配置选项。

资源类型

本扩展模块未定义任何资源类型。

预定义常量

本扩展模块未定义任何常量。

范例

在本例子中,先定义一个基础类和一个扩展类。基础类描述的是普通的蔬菜(Vegetable),它是否可食用(is_edible)以及它是什么颜色(what_color)。子类 Spinach 增加了“烹调”的方法(cook_it)和“获知它是否已被烹调了”的方法(is_cooked)。

例子 1. classes.inc

<?php

// 基类及其成员属性与方法
class Vegetable {

   var
$edible;
   var
$color;

   function
Vegetable($edible, $color="green")
   {
      
$this->edible = $edible;
      
$this->color = $color;
   }

   function
is_edible()
   {
       return
$this->edible;
   }

   function
what_color()
   {
       return
$this->color;
   }

}
// Vegetable 类定义结束

// 扩展基类
class Spinach extends Vegetable {

   var
$cooked = false;

   function
Spinach()
   {
      
$this->Vegetable(true, "green");
   }

   function
cook_it()
   {
      
$this->cooked = true;
   }

   function
is_cooked()
   {
       return
$this->cooked;
   }

}
// Spinach 类定义结束

?>

接着,从这些对象中创建两个对象实例,打印出它们的相关信息,包括它们的起源。还定义了一些实用函数,它们主要是为了让变量的输出好看些。

例子 2. test_script.php

<pre>
<?php

include "classes.inc";

// utility functions

function print_vars($obj)
{
   foreach (
get_object_vars($obj) as $prop => $val) {
       echo
"\t$prop = $val\n";
   }
}

function
print_methods($obj)
{
  
$arr = get_class_methods(get_class($obj));
   foreach (
$arr as $method) {
       echo
"\tfunction $method()\n";
   }
}

function
class_parentage($obj, $class)
{
   if (
is_subclass_of($GLOBALS[$obj], $class)) {
       echo
"Object $obj belongs to class " . get_class($$obj);
       echo
" a subclass of $class\n";
   } else {
       echo
"Object $obj does not belong to a subclass of $class\n";
   }
}

// instantiate 2 objects

$veggie = new Vegetable(true, "blue");
$leafy = new Spinach();

// print out information about objects
echo "veggie: CLASS " . get_class($veggie) . "\n";
echo
"leafy: CLASS " . get_class($leafy);
echo
", PARENT " . get_parent_class($leafy) . "\n";

// show veggie properties
echo "\nveggie: Properties\n";
print_vars($veggie);

// and leafy methods
echo "\nleafy: Methods\n";
print_methods($leafy);

echo
"\nParentage:\n";
class_parentage("leafy", "Spinach");
class_parentage("leafy", "Vegetable");
?>
</pre>

上边例子中重点要注意的是对象 $leafySpinach 类的一个实例,而 Spinach 类是 Vegetable 类的一个子类,因此上边脚本的最后部分将会输出:

[...]
Parentage:
Object leafy does not belong to a subclass of Spinach
Object leafy belongs to class spinach a subclass of Vegetable

目录
call_user_method_array --  调用一个用户方法,同时传递参数数组[已停用]
call_user_method --  调用特定对象的用户方法[已停用]
class_exists -- 检查类是否已定义
get_class_methods -- 返回由类的方法名组成的数组
get_class_vars --  返回由类的默认属性组成的数组
get_class -- 返回对象的类名
get_declared_classes -- 返回由已定义类的名字所组成的数组
get_declared_interfaces --  Returns an array of all declared interfaces
get_object_vars -- 返回由对象属性组成的关联数组
get_parent_class -- 返回对象或类的父类名
interface_exists -- Checks if the interface has been defined
is_a --  如果对象属于该类或该类是此对象的父类则返回 TRUE
is_subclass_of --  如果此对象是该类的子类,则返回 TRUE
method_exists -- 检查类的方法是否存在
property_exists --  Checks if the object or class has a property


add a note add a note User Contributed Notes
Classes/Objects 类/对象函数
Bardo
18-Nov-2006 07:38
<?
//this wil give you a nice view over your used classes.
//in my config-file i've loaded my classes
//this script gives you a class-diagram
//still need to something about the javascript feature...
//hope it helps you :)

include("config.php");

function drawClass($c){
   if($c['beschrijving']){
       echo "<table style=\"background-color: #FFFFCC; border: 1px solid black;\">".$c['beschrijving'];
   }
   foreach($c as $k=>$sub){
       if($k!="beschrijving"){
           echo "<tr><td>";
           drawClass($c[$k],($i+1));
           echo "</td></tr>";
       }
   }
   if($c['beschrijving']){
       echo "</table><br>";
   }
}
?>
<html>
<head>
<style>
*{
   font-family: Verdana;
   font-size: 10pt;
}
</style>
<script>
var mem=new Array();
function up(id){
   if(!mem[id]) mem[id]=true;
   else mem[id]=false;
   if(mem[id])    document.getElementById(id).style.visibility="hidden";
   else document.getElementById(id).style.visibility="visible";
}
</script>
</head>
<body>

<?
$list=array();
$classes=get_declared_classes();
foreach($classes as $k=>$v){
   $vars_p=array();
   $meth_p=array();
   $parent=array($v);
   $parent_tmp=$v;
   $target= &$list;
   while($parent_tmp=get_parent_class($parent_tmp)){
       array_push($parent,$parent_tmp);
       $vars_p=array_merge($vars_p,get_class_vars($parent_tmp));
       $meth_p=array_merge($meth_p,get_class_methods($parent_tmp));
   }
   for($i=count($parent)-1;$i>=0;$i--){
       if(!is_array($target[$parent[$i]])){
           $target[$parent[$i]]=array();
       }
       $target= &$target[$parent[$i]];
   }

   $str="<tr><td style=\"background-color: #FFCC66;\"><button onclick=\"up('$v');\">UP</button>";
   foreach($parent as $itt=>$p) $str.=($itt>0?"::":"").$p;
   $str.="</td></tr><tr><td>";
   $str.="<table style=\"background-color: #FFFFCC; width: 300px;\" id=\"".$v."\">";
  
   $vars=get_class_vars($v);
   foreach($vars as $l=>$w){
       if(!is_numeric(array_search($l,array_keys($vars_p)))){
           $str.="<tr><td>var ".$l.($w?"=".$w:"").";</td></tr>";
       }
   }
   $meth=get_class_methods($v);
   foreach($meth as $l=>$w){
       if(!is_numeric(array_search($w,$meth_p))){
           $str.="<tr><td>function ".$w."(){}</td></tr>";
       }
   }

   $str.="</table></td></tr>";

   $target['beschrijving']=$str;
}

drawClass($list);
?>

</body>
</html>
kim dot hermansson at gmail dot com
01-Sep-2006 08:40
Note to the solution by 'zabmilenko at hotmail dot com':

Good idea but encapsulation is a better technique which is also more portable and reliable.

You can pass an object which is based on an interface/superclass to the common DB interface.

For instance:
<?php

class DB_Common {
 
// data can be either an uri or array depending on how you construct it
  // e.g. data = array('host' => 'x.x.x.x', 'port' => '65535', 'etc' => '...')
 
function connect($data) { die("must be overriden by subclass"); }
  function
disconnect() { die("..."); }
  function
query($query) { die("..."); }
}

class
DB_MySQL extends DB_Common { /* override methods here */ }
class
DB_PgSQL extends DB_Common { /* same here */

class DB_Frontend {
  var
$errmsg = array('DB_Common subclass-string or object required');
  var
$_db;
 
// PHP4/5 compat
 
function DB_Frontend($arg) { $this->__construct($arg); }
  function
__construct($arg) {
   if(
is_object($arg) && is_a($arg, 'DB_Common') )
    
$this->_db = $arg;
   else
   {
     if( !
is_string($arg) )
       die(
$this->errmsg[0]);
    
$arg = "DB_$arg";
     if( !
class_exists($arg) )
       die(
$this->errmsg[0]);
    
$this->_db = new $arg;
    
// use is_subclass_of(object, string) for compat with older versions
    
if( !is_subclass_of($arg, 'DB_Common') )
       die(
$this->errmsg[0]);
   }
  }
 
// add connect, disconnect that simply calls $this->_db->method() instead
 
function getOne($quey) {
  
// preprocess query if needed
  
$result = $this->_db->query($query);
  
// process result if needed
  
return $result;
  }
 
/* add more methods if needed */
}

// string parameter
$frontend = new DB_Frontend('MySQL');

// object parameter
$backend = new DB_MySQL();
$frontend = new DB_Frontend($backend);

?>

You can also use a static DB class. If you see PEAR::DB you will see how all this is quite equal to eachother. The main difference is the frontend class which should encapsulte the backend.

Suggestions and/or comments appreciated.
beconfused at googlemail dot com
20-Jul-2006 06:18
function from Tobias K.....
stdClass Object to XML

xml:
[CODE]
"$xml<?xml version='1.0' standalone='yes'?>" .
"<movies>" .
   "<movie eur=\"10.00\">" .
       "<title>PHP: Behind the Parser</title>" .
       "<characters>" .
           "<character>" .
               "<name>Ms. Coder</name>" .
               "<actor>Onlivia Actora</actor>" .
           "</character>" .
           "<character>" .
               "<name>Mr. Coder</name>" .
               "<actor>El ActÓr</actor>" .
           "</character>" .
       "</characters>" .
       "<plot>" .
           "Text bla bla." .
       "</plot>" .
       "<rating ulf=\"integer\">7</rating>" .
       "<rating type=\"stars\">5</rating>" .
   "</movie>" .
"</movies>";
[/CODE]

stdObject:
[CODE]
stdClass Object
(
   [movie] => stdClass Object
       (
           [title] => PHP: Behind the Parser
           [characters] => stdClass Object
               (
                   [character] => Array
                       (
                           [0] => stdClass Object
                               (
                                   [name] => Ms. Coder
                                   [actor] => Onlivia Actora
                               )

                           [1] => stdClass Object
                               (
                                   [name] => Mr. Coder
                                   [actor] => El ActÓr
                               )

                       )

               )

           [plot] => Text bla bla.
           [rating] => Array
               (
                   [0] => stdClass Object
                       (
                           [_] => 7
                           [ulf] => integer
                       )

                   [1] => stdClass Object
                       (
                           [_] => 5
                           [type] => stars
                       )

               )

           [eur] => 10
       )

)
[/CODE]

Aufruf:
[PHP]
$s = htmlentities(asXML($r,"movies",array(
   "movies/movie" => "eur",
   "movies/movie/rating" => "ulf type"
)));
[/PHP]

Funktion:
[PHP]
function asXML($obj, $name, $attrNames=NULL, &$xml="", $path="", $depth=0) {
   if (is_array($obj)) {
       foreach ($obj as $v)
           asXML($v,$name,$attrNames,$xml,$path,$depth);
   } else {
       if ($path) $xml .= "\n";
       $path = $path ? "$path/$name" : $name;
       $indent = str_repeat(" ",$depth*4);
       $xml .= "$indent<$name";
       $attr = $attrNames[$path];
       if ($attr) {
           if (!is_object($obj))
               throw new Exception("asXML(): ".
                       "$path is no object, so it cannot hold attributes");
           $attr = explode(" ",$attr);
           foreach ($attr as $key)
               if ($obj->$key) $xml .= " $key=\"{$obj->$key}\"";
       }
       $xml .= ">";
       if (is_object($obj)) {
           $xml .= $obj->_;
           $len = strlen($xml);
           foreach (get_object_vars($obj) as $key => $value)
               if ($key!="_" && (!is_array($attr) || !in_array($key,$attr)))
                   asXML($value,$key,$attrNames,$xml,$path,$depth+1);
           if ($len!=strlen($xml)) $xml .= "\n$indent";
       } else {
           $xml .= $obj;
       }
       $xml .= "</$name>";
   }
   return $xml;
}
[/PHP]
Chris
25-Oct-2005 03:06
You could reformat your query to use the 'as colname'
<?  $db->query("select found_rows() as found_rows"); ?>
pascal dot poncet at netconsult dot com
13-Oct-2005 07:21
Subject: using "sql_calc_found_rows" in a MySQL query while exploiting result in a PHP db class object.

Hello,

There is a nice function in MySQL that allows to know how many records would have been returned if no "where" clause were set : SQL_CALC_FOUND_ROWS.

If you have create a db object to collect the returned lines, you will be a little perplex when trying to call the result of this function.

Why ?
Simply because the returned field's name is "found_rows()" and obviously it's not possible to call something like :

<?php $result->found_rows() ?>

...as it will try to acces a method, not a property !

Then, the only way to get the right result seems to be the use of a class function, like :

<?php
  $db
->query("select found_rows()");
 
$count=current(get_object_vars(current($db->result)));
?>

Of course, if somebody found an other way to solve it, like a special syntax (see the one used with curled arrays in a string), I'm really open to discuss.

Good luck,
Pascal
ia [AT] zoznam [DOT] sk
02-Aug-2005 10:55
as for zabmilenko's solution:
wouldn't it be better to create it this way?

<?php
// The base class for all db classes
class DB {
 protected
$connectId;
}

// Class for MySQL, which extends base class
class MySQL extends DB {
 function
connect () {
 
$this->connectId = mysql_connect (...);
 }
}

// Class for PostgreSQL, which extends base class
class pgSQL extends DB {
 function
connect () {
 
$this->connectId = pg_connect (...);
 }
}

// and then call constructor like this:
$dbName = "MySQL";
$db = new $dbName ( ... );
// ... which creates an object of class MySQL
?>
zabmilenko at hotmail dot com
27-Jun-2005 12:08
((PHP5))

I wanted to dynamically choose an extender for a class.  This took awhile of playing with it but I came up with a solution.  Note that I can't verify how safe it is, but it appears to work for me.  Perhaps someone else can shed light on the details:

<?php

class A { var $value = "Class A\n"; }
class
B { var $value = "Class B\n"; }

// Uncomment which extender you want.  You can use variables as well.
// define('__EXTENDER__', 'A');
  
define('__EXTENDER__', 'B');

// Use eval to create a wrapper class.
eval('class EXTENDER extends '. __EXTENDER__ . ' { }');

class
C extends EXTENDER
{
  function
__construct()
  {
     echo
$this->value;
  }
}

$t = new C;

?>

Outputs:  Class B

Practical application:  I have a database abstraction system that has individual classes for mysql, pgsql, et al.  I want to be able to create a global db class that extends one of the individual db classes depending on the application configuration.

I know that there are probably much better ways of doing this but I haven't reached that level when it comes to classes.
cjones
03-Mar-2005 01:27
If anyone is interested in looking for a way to dynamically load existing objects into a class, here is what I found very useful.

//---------------------------------------------------------
// Dynamically load External Objects into a class

  function objRef ( &$obj ) {   
   eval("\$this->obj_".get_class($obj)." = \$obj;");
  }
//---------------------------------------------------------
// Reference by using: $this->obj_[object Name]->[var|f{}]

Example:

class date {  function date ( ) { $this->date = "March 3rd"; } }
class time {  function time ( ) { $this->time = "12:30pm"; } }

class show {
   function objRef ( &$obj ){
       eval("\$this->obj_".get_class($obj)." = \$obj;");
   }
   function test ( $var ){
       echo "$var".$this->obj_date->date." @ ".$this->obj_time->time;
   }
}

$date = new date;
$time = new time;
$show = new show;
   $show->objRef($date);
   $show->objRef($time);
   $show->test("Time Now => ");

// Output: Time Now => March 3rd @ 12:30pm

I found the prefix 'obj_' before the class name useful because it helped me to automatically identify external object references when scanning through my scripts. You can omit this if you want. Hope this helps someone.
http://sc.tri-bit.com/ StoneCypher
02-Mar-2005 10:25
to covertka at muohio dot edu and pillepop2003 at yahoo dot de:

There's a much easier solution to getting a class' name for working with a factory function.  Let's assume you're doing something like this:

<?php

 
function FactoryFunction($whatever, $instancedata) {

   switch (
$whatever) {
     case
'stuff'      : return new Stuff($instancedata);
     case
'otherstuff' : return new Otherstuff($instancedata);
   }

  }

?>

Now, consider the named parameter idiom and remember that PHP uses hashes for everything; as a result make the following changes:

<?php

 
function FactoryFunction($whatever, $instancedata) {

   switch (
$whatever) {

     case
'stuff'      : return array('typeis'=>'stuff',      'instance'=>new Stuff($instancedata));
     case
'otherstuff' : return array('typeis'=>'otherstuff', 'instance'=>new Otherstuff($instancedata));

   }

  }

?>

Nice 'n simple.  It seems that what the original poster wanted was something like C++ static data members; unfortunately as PHP4 has no static variables at all, there would need to be significant language change to support static-like behavior.  If you move to PHP5, the static keyword solves your problem cleanly.
covertka at muohio dot edu
02-Jan-2005 05:27
To pillepop2003 at yahoo dot de:

I have the same issue.  I have a base class that manages database tasks for a number of child classes.  One of the functions in the base class is a find() method that returns instances of the child classes.  Since find() is usually called as a static method, it needs to know the name of the child class.  As you've found, this appears to be impossible to get in an easy fashion.

The only way I've found to get the child class name is to use the debug_traceback() function.  This requires me to have a find() method in every child class, but it does work.

Here's an example:

<?php
 
require_once("Application.php");

  class
parentClass {
   function
find() {
    
$className = NULL;
     foreach (
debug_backtrace() as $bt) {
       if (
$bt['function'] == __FUNCTION__) {
        
$className = $bt['class'];
       }
     }

    
// here should be some code to find the proper id, let's assume it was id 1
    
$id = 1;
     return new
$className($id);
   }
  }
 
  class
foo extends parentClass {
   function
__construct($id) {
    
$this->id = id;
   }
  
   function
find() {
     return
parent::find();
   }
  }
 
  class
bar extends parentClass {
   function
__construct($id) {
    
$this->id = id;
   }

   function
find() {
     return
parent::find();
   }
  }
 
 
$a = foo::find();
 
printf("Type for \$a: %s<br/>\n", get_class($a));
 
$b = bar::find();
 
printf("Type for \$b: %s<br/>\n", get_class($b));
?>
iparanoid at gmx dot de
04-Aug-2004 11:17
To pillepop2003 at yahoo dot de:

It seems to me if there really is no nice way to get the class name in an un-instanciated class, there is a workaround in PHP5 though using static/class variables.

Example:

<?php

class myFoo
{
   static
$__ClassName = __CLASS__;

   static function
getClassName()
   {
       return
myFoo::$__ClassName;
   }
};

class
myFooExtended extends myFoo
{
   function
__construct()
   {
      
myFooExtended::$__ClassName = __CLASS__;
   };
};

?>

However, you'll need to have at least instanciated an object of the class myFooExtended before calling getClassName or introduce some other initialization (the class variable will need to be set at some point to __CLASS__ in the sub-class).
greg at doutromundo dot com
05-Jul-2004 07:58
As programmers, you're probably more organized than me, but, I do try and maintain some order in my classes and codes and separate them in "packages" as in java.
This helped me keep them organized but caused havok when trying to use them, so what I did was to create a class that handles the loading of classes (which I instanciate in all pages) along with my error handling class all bundled up. This way, I can load my classes with a command similar to
$baseClass->loadClass("package","className"[,"constructor"]);

the function responsible for this has some checking to see if they are loaded and stuff like that...

function loadClass($packageName,$className,$constructor=""){
  // if you dont have a constructor declare any function inside
  // the class
  if ($constructor==""){
   $constructor=$className;
  }
  if(!is_callable(array($className,$constructor))){