【问题标题】:Type casting for user defined objects用户定义对象的类型转换
【发布时间】:2009-07-18 09:36:08
【问题描述】:

就像我们对 __ToString 所做的那样,有没有一种方法可以定义一个转换方法?

$obj = (MyClass) $another_class_obj;

【问题讨论】:

  • 你为什么要在 PHP 中转换任何东西?无论如何,你可以调用任何你想要的方法。
  • 如果类中不存在这些方法,则不能调用它们——您可能需要将一个用户类类型转换为另一个,以便使用 -> 运算符直接调用函数跨度>
  • 乔希,在这种情况下,应用程序流程一定有问题。
  • 我觉得你的判断可能仓促;但是我同意 gnarf 创建新对象的解决方案更好,因为它允许在类之间进行转换。

标签: php casting


【解决方案1】:

在 php 中无需键入 cast。


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

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

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

class Alpha {}

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

如果我们现在这样做:

Alpha a = new Beta();

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

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 农场。

以下内容对 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 中的变量是没有类型的,所以这不仅是不可能做到的,而且一开始就问是一件无意义的事情。

【讨论】:

  • 那么,不要一个人听。
  • PHP 中的变量是无类型的;只有它们引用的对象才有类型。因此,您不能在 php 中对变量进行类型转换。
  • 这是一个很好的答案。我觉得你可以通过提到 PHP 中的值有类型来改进它。这很有趣,因为值的类型可能会改变——一个 Alpha 对象可以被告知“成为一个 Beta 对象”。例如,Python 和 Javascript 支持这一点(在 Python 中通过更改其 __class__ 字段)。然而,PHP 没有,并且有一个静态的类层次结构。因此,虽然允许 PHP 值更改其类型是有意义的,但语言中不允许这样做。
  • troelskn,我同意,这是一个很好的答案,并且非常清楚地解释了类型转换以及 PHP 中缺少它。
  • @elviejo 不,这就是重点。您会收到运行时错误,但不是编译时错误。
【解决方案2】:

虽然在 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;
        }
    }
}

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

【讨论】:

    【解决方案3】:

    我重新编写了 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 中强制转换”的混蛋们嘘声。斗牛曲棍球。强制转换是面向对象生活的重要组成部分,我希望我能找到比丑陋的序列化 hack 更好的方法。

    非常感谢乔希!

    【讨论】:

    • 请注意,如果转换为的类不是父类,我会将抛出的异常添加到 changeClass 中。这将包括不存在的类。但是我上面的代码几乎是我所知道的工作演员中最简单的代码。
    • 我会在 php 中使用强制转换的唯一方法是让 IDE 知道对象的类型。这样 IDE 可以自动完成类实例的属性。仅此而已。
    • 嗯,这一切都很好,对你有好处 Janov,但如果你正在构建一个不太冗长的复杂界面,你有时需要一些技巧。我不得不为我创建的数据库抽象层使用类转换。否则将无法充分处理子类化。
    • php 不强制转换意味着 php 没有本地方法来强制转换。序列化反序列化是解决问题的一种方法。
    • 是否有可能需要针对 PHP5.3 进行修改?
    【解决方案4】:

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

    /**
     * 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)));
        }
    }
    

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

    【讨论】:

    • 这不是类型转换。那是改变对象的类型 - 而不是变量。在 PHP 中,你不能进行类型转换,因为变量没有类型。
    • 我同意,我的函数改变了对象的类。这是问题作者的意图。根据 PHP 手册,变量 do 具有类型并且可以进行类型转换:us.php.net/manual/en/…
    • 还不错;您可以更改基元的类型。但是您不能更改对象的类型。无论手册中的措辞如何,变量都在 php.ini 中具有类型。它们指的是具有类型的值。
    • 我明白你的意思——我将编辑我的帖子以明确表示它改变了对象的类。
    • 为什么有这么多赞? toma 的原始函数有一个错误(他错过了preg_replace 之后的(),但是这个甚至不能正常工作。您使用了从未定义的变量$class_type。也许我遗漏了一些东西,但在我看来,这看起来会让有缺陷的代码变得更糟。
    【解决方案5】:

    如果您只需要类型提示的强制转换,那么此方法有效。

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

    是的。

    【讨论】:

    • 无需在instanceof 之前调用is_object()——它适用于任何输入,包括非对象标量和空值。
    【解决方案6】:

    我不相信 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);
    

    是一种处理方式。

    【讨论】:

      【解决方案7】:

      我认为您需要进行类型转换才能制作更好的 IDE。但是 php 语言本身不需要类型转换,但它支持对变量中的值进行运行时类型更改。看看自动装箱和拆箱。这就是 php 固有的功能。很抱歉,没有比现在更好的 IDE 了。

      【讨论】:

        【解决方案8】:

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

        从 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');
        

        【讨论】:

          【解决方案9】:

          即使在 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.php 中第 xxx 行的意外 '$foo' (T_VARIABLE)

          所以你基本上不能这样做,但再想一想,也许你只想在类的其他公共功能之上添加一个新功能?

          您可以使用 Wrapper 做到这一点

          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();
          

          一个 - pub - one-par1

          一个 - pub - one-par2

          包装器 - 新功能

          如果您想取出受保护的财产怎么办?

          你也可以这样做

          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);
          

          你会得到

          一个

          你可以用同样的方式得到保护

          【讨论】:

            【解决方案10】:

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

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

            从文件返回有点难看,并且不允许类型提示,所以这是一个完美的例子,说明如何使用类型转换助手实现智能感知。

            【讨论】:

              【解决方案11】:

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

                  public static function cast($sourceObject)
              {
                  $destinationObject = new self();
                  try {
                      $reflectedSource = new ReflectionObject($sourceObject);
                      $reflectedDestination = new ReflectionObject($destinationObject);
                      $sourceProperties = $reflectedSource->getProperties();
                      foreach ($sourceProperties as $sourceProperty) {
                          $sourceProperty->setAccessible(true);
                          $name = $sourceProperty->getName();
                          $value = $sourceProperty->getValue($sourceObject);
                          if ($reflectedDestination->hasProperty($name)) {
                              $propDest = $reflectedDestination->getProperty($name);
                              $propDest->setAccessible(true);
                              $propDest->setValue($destinationObject, $value);
                          } else {
                              $destinationObject->$name = $value;
                          }
                      }
                  } catch (ReflectionException $exception) {
                      return $sourceObject;
                  }
                  return $destinationObject;
              }
              

              【讨论】:

              • 请写一个更完整的答案。一行句子和指向 StackOverflow 外部的链接对于寻找答案的人来说不是很有帮助。
              【解决方案12】:

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

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

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

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

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

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

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


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

              【讨论】:

                猜你喜欢
                • 2011-04-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-11-27
                • 1970-01-01
                相关资源
                最近更新 更多