为用户定义的对象键入转换

时间:2009-07-18 09:36:09

标签: php casting

就像我们使用__ToString一样,有没有办法定义一个方法来进行投射?

$obj = (MyClass) $another_class_obj;

12 个答案:

答案 0 :(得分:99)

无需在php中输入强制转换。


编辑: 由于这个主题似乎引起了一些混乱,我想我会详细说明一下。

在Java等语言中,有两种可能带有类型的东西。编译器有关于类型的概念,运行时对类型有另一种想法。编译器类型与变量相关联,而运行时引擎跟踪值的类型(分配给变量)。变量类型在编译时是已知的,而值类型仅在运行时已知。

如果一段输入代码违反了编译器类型系统,编译器将禁止并停止编译。换句话说,编译违反静态类型系统的代码是不可能的。这会捕获某类错误。例如,采用以下(简化的)Java代码:

class Alpha {}

class Beta extends Alpha {
  public void sayHello() {
    System.out.println("Hello");
  }
}

如果我们现在这样做了:

Alpha a = new Beta();

我们没问题,因为BetaAlpha的子类,因此是a类型的变量Alpha的有效值。但是,如果我们继续这样做:

a.sayHello();

编译器会给出错误,因为方法sayHello不是Alpha的有效方法 - 无论我们知道a实际上是Beta

输入类型转换:

((Beta) a).sayHello();

这里我们告诉编译器变量a应该 - 在这种情况下 - 被视为Beta。这被称为类型铸造。这个漏洞非常有用,因为它允许语言中的多态性,但显然它也是各种违反类型系统的后门。为了保持某种类型的安全性,因此存在一些限制;您只能转换为相关的类型。例如。上下层次结构。换句话说,您将无法转换为完全不相关的类Charlie

重要的是要注意所有这些都发生在编译器中 - 也就是说,它发生在代码甚至运行之前。 Java仍然可以进入运行时类型错误。例如,如果您这样做:

class Alpha {}

class Beta extends Alpha {
  public void sayHello() {
    System.out.println("Hello");
  }
}

class Charlie extends Alpha {}

Alpha a = new Charlie();
((Beta) a).sayHello();

以上代码对编译器有效,但在运行时,您将获得异常,因为从BetaCharlie的强制转换是不兼容的。

同时,回到PHP-farm。

以下内容对PHP编译器有效 - 它很乐意将其转换为可执行字节代码,但是会出现运行时错误:

class Alpha {}

class Beta extends Alpha {
  function sayHello() {
    print "Hello";
  }
}
$a = new Alpha();
$a->sayHello();

这是因为PHP变量没有类型。编译器不知道哪个运行时类型对变量有效,因此它不会尝试强制执行它。您也没有像在Java中那样明确指定类型。有类型提示,是的,但这些只是运行时合同。以下内容仍然有效:

// reuse the classes from above
function tellToSayHello(Alpha $a) {
  $a->sayHello();
}
tellToSayHello(new Beta());

即使PHP 变量没有类型,仍然可以。 PHP的一个特别有趣的方面是可以更改值的类型。例如:

// The variable $foo holds a value with the type of string
$foo = "42";
echo gettype($foo); // Yields "string"
// Here we change the type from string -> integer
settype($foo, "integer");
echo gettype($foo); // Yields "integer"

此功能有时与类型转换相混淆,但这是用词不当。类型仍然是值的属性,类型更改在运行时发生 - 而不是在编译时发生。

在PHP中,改变类型的能力也非常有限。只能在简单类型之间更改类型 - 而不是对象。因此,不可能将类型从一个类更改为另一个类。您可以创建新对象并复制状态,但无法更改类型。 PHP在这方面有点像局外人;其他类似的语言将类视为比PHP更具动态性的概念。

PHP的另一个类似功能是您可以将值克隆为新类型,如下所示:

// The variable $foo holds a value with the type of string
$foo = "42";
echo gettype($foo); // Yields "string"
// Here we change the type from string -> integer
$bar = (integer) $foo;
echo gettype($bar); // Yields "integer"

从语法上讲,这看起来很像用静态类型语言编写类型转换器。因此,它经常与类型转换混淆,即使它仍然是运行时类型转换。

总结:类型转换是一种更改变量类型的操作(不是值)。由于变量在PHP中没有类型,因此不仅无法做到,而且首先要问一个荒谬的事情。

答案 1 :(得分:8)

虽然不需要在PHP中键入强制类型,但您可能会遇到想要将父对象转换为子对象的情况。

简单

//Example of a sub class
class YourObject extends MyObject {
    public function __construct(MyObject $object) {
        foreach($object as $property => $value) {
            $this->$property = $value;
        }
    }
}


$my_object = new MyObject();
$your_object = new YourObject($my_object);

所以你要做的就是将父对象传递给子对象的构造函数,然后让构造函数复制属性。您甚至可以根据需要过滤/更改它们。

高级

//Class to return standard objects
class Factory {
    public static function getObject() {
        $object = new MyObject();
        return $object;
    }
}

//Class to return different object depending on the type property
class SubFactory extends Factory {
    public static function getObject() {
        $object = parent::getObject();
        switch($object->type) {
        case 'yours':
            $object = new YourObject($object);
            break;
        case 'ours':
            $object = new OurObject($object);
            break;
        }
        return $object;
    }
}

//Example of a sub class
class YourObject extends MyObject {
    public function __construct(MyObject $object) {
        foreach($object as $property => $value) {
            $this->$property = $value;
        }
    }
}

它不是类型转换,但它可以满足您的需求。

答案 2 :(得分:7)

这是一个更改对象类的函数:

/**
 * Change the class of an object
 *
 * @param object $obj
 * @param string $class_type
 * @author toma at smartsemantics dot com
 * @see http://www.php.net/manual/en/language.types.type-juggling.php#50791
 */
function changeClass(&$obj,$new_class)
{
    if(class_exists($class_type,true))
    {
        $obj = unserialize(preg_replace("/^O:[0-9]+:\"[^\"]+\":/i",
            "O:".strlen($class_type).":\"".$new_class."\":", serialize($obj)));
    }
}

如果不清楚,这不是我的功能,它来自http://www.php.net/manual/en/language.types.type-juggling.php#50791上的“toma at smartsemantics dot com”的帖子

答案 3 :(得分:7)

我重写了Josh发布的函数(由于未定义的$ new_class变量,这将会出错)。这就是我得到的:

function changeClass(&$obj, $newClass)
{   $obj = unserialize(preg_replace // change object into type $new_class
    (   "/^O:[0-9]+:\"[^\"]+\":/i", 
        "O:".strlen($newClass).":\"".$newClass."\":", 
        serialize($obj)
    ));
}

function classCast_callMethod(&$obj, $newClass, $methodName, $methodArgs=array())
{   $oldClass = get_class($obj);
    changeClass($obj, $newClass);

    // get result of method call
    $result = call_user_func_array(array($obj, $methodName), $methodArgs);
    changeClass(&$obj, $oldClass);  // change back
    return $result;
}

它就像你期望一个类演员一样工作。你可以为访问班级成员建立类似的东西 - 但我认为我不会需要它,所以我会留给其他人。

对所有说“php不会投射”或“你不需要在php中投射”的混蛋嘘声。 Bullhockey。 Casting是面向对象生活的重要组成部分,我希望我能找到比丑陋的序列化黑客更好的方法。

谢谢Josh!

答案 4 :(得分:1)

我不相信PHP中有一个重载运算符来处理它,但是:

<?php

class MyClass {

  protected $_number;

  static public function castFrom($obj) {
    $new = new self();
    if (is_int($obj)) {
      $new->_number = $obj;
    } else if ($obj instanceOf MyNumberClass){
      /// some other type of casting
    }
    return $new;
  }
}

$test = MyClass::castFrom(123123);
var_dump($test);

是处理它的一种方法。

答案 5 :(得分:1)

我认为您需要输入强制转换才能创建更好的IDE。但是php语言本身并不需要类型转换,但它支持运行时类型更改变量中的值。看看自动装箱和拆箱。这就是php固有的功能。很遗憾没有比IDE更好的了。

答案 6 :(得分:1)

如果您正在进行类型提示的转换,则可以使用。

if( is_object($dum_class_u_want) && $dum_class_u_want instanceof ClassYouWant )
{
    // type hints working now
    $dum_class_u_want->is_smart_now();
}

是的。

答案 7 :(得分:0)

我认为您的意思是类型提示

从PHP 7.2开始,您可以在函数中键入提示参数:

function something(Some_Object $argument) {...} # Type-hinting object on function arguments works on PHP 7.2+

但是您不能像这样键入提示:

(Some_Object) $variable = get_some_object($id); # This does not work, even in PHP 7.2

在PHP中未正式实现类型提示对象的替代方法是:

$variable = get_some_object($id); # We expect Some_Object to return
is_a($argument, 'Some_Object') || die('get_some_object() function didn't return Some_Object');

答案 8 :(得分:0)

即使在PHP 7.2上,如果您尝试进行简单的类型转换,例如

class One
{
    protected $one = 'one';
}

class Two extends One
{
    public function funcOne()
    {
        echo $this->one;
    }
}


    $foo = new One();
    $foo = (Two) $foo;
    $foo->funcOne();

您会收到这样的错误

  

PHP解析错误:语法错误,第xxx行上xxx.php中出现意外的'$ foo'(T_VARIABLE)

所以您基本上不能做到这一点,但是再想一想,也许您只想在该类的其他公共功能之上只需要一个新功能?

您可以使用包装器

class One
{
    protected $one;
    public function __construct(string $one)
    {
        $this->one = $one;
    }
    public function pub(string $par){
        echo (__CLASS__ . ' - ' . __FUNCTION__ . ' - ' . $this->one . '-' .$par);
    }
}

class Wrapper
{
    private $obj;

    public function __construct(One $obj)
    {
        $this->obj = $obj;
    }
    public function newFunction()
    {
        echo (__CLASS__ . ' - ' . __FUNCTION__);
    }
    public function __call($name, $arguments)
    {
        return call_user_func_array([$this->obj, $name], $arguments);
    }
}

    $foo = new One('one');
    $foo->pub('par1');
    $foo = new Wrapper($foo);
    $foo->pub('par2');
    $foo->newFunction();
  

一个-客栈-one-par1

     

一个-客栈-one-par2

     

包装器-newFunction

如果要撤出受保护的财产怎么办?

您也可以这样做

class One
{
    protected $one;

    public function __construct(string $one)
    {
        $this->one = $one;
    }
}


    $foo = new One('one');
    $tmp = (new class ($foo) extends One {
            protected $obj;
            public function __construct(One $obj)
            {
                $this->obj = $obj;
                parent::__construct('two');
            }
            public function getProtectedOut()
            {
                return $this->obj->one;
            }
        } )->getProtectedOut();

    echo ($tmp);

你会得到

  

一个

您可以用相同的方式获取保护

答案 9 :(得分:0)

我主要需要类型转换才能启用智能感知-所以我只创建一个类型转换助手:

function castToTest($val): Test
{
    return $val;
}
$test = castToTest(require("someFile.php"));

从文件返回很丑陋,并且不允许类型提示,因此这是如何使用类型强制转换助手实现智能感知的完美示例。

答案 10 :(得分:0)

您可以创建两个反射对象,然后将值复制到其他示例,可以在https://github.com/tarikflz/phpCast

中找到
scipy.special.expn

答案 11 :(得分:0)

被选为正确的答案实际上是错误的答案(或者,问题被误解了)。

这个问题的正确解释与面向对象设计范式的基本原则有关,根据该原则,子类应该能够分配给其父类的变量,或者分配给它的接口的变量由类实现。

因为这是面向对象设计的基础,不能说A语言需要,B语言不需要。

正确答案是,迄今为止,PHP 缺乏面向对象设计的基本要求。

为了使代码符合 A) 依赖倒置原则 (DIP),以及 B) 接口隔离原则(S.O.L.I.D 中的“I”)

... PHP 必须允许使用具体类实现的接口名称对变量进行类型提示,以便只能使用实现(服务)类的特定于接口的功能。

这是面向对象设计的基本原则,如果不能声明特定类型的接口代码的变量的类型,就不能符合(也不能享受)DIP和接口隔离原则。

>

另外,动态绑定总是在运行时完成,而不是在编译时完成,因此 PHP 在提供 OO 范式的基本元素时应该没有问题。