Приведение типов для пользовательских объектов - PullRequest
50 голосов
/ 18 июля 2009

Так же, как мы делаем с __ToString, есть ли способ определить метод для приведения?

$obj = (MyClass) $another_class_obj;

Ответы [ 9 ]

100 голосов
/ 18 июля 2009

Нет необходимости набирать приведение в php.


Редактировать: Поскольку эта тема, кажется, вызывает некоторую путаницу, я подумал, что немного уточню.

В таких языках, как Java, есть две вещи, которые могут иметь тип. Компилятор имеет представление о типе, а время выполнения - другое представление о типах. Типы компиляторов привязаны к переменным, тогда как механизм времени выполнения отслеживает тип значений (которые назначаются переменным). Типы переменных известны во время компиляции, тогда как типы значений известны только во время выполнения.

Если часть входного кода нарушает систему типов компиляторов, компилятор прервет компиляцию. Другими словами, невозможно скомпилировать фрагмент кода, который нарушает статическую систему типов. Это ловит определенный класс ошибок. Например, возьмем следующий фрагмент (упрощенного) кода Java:

class Alpha {}

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

Если бы мы сейчас это сделали:

Alpha a = new Beta();

все будет в порядке, поскольку Beta является подклассом Alpha и, следовательно, допустимым значением для переменной 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();

Приведенный выше код действителен для компилятора, но во время выполнения вы получите исключение, поскольку приведение от Beta к Charlie несовместимо.

А пока вернемся на 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 переменные не имеют типа, это не только невозможно, но и бессмысленно спрашивать.

8 голосов
/ 18 января 2011

Хотя в PHP нет необходимости вводить приведение типов, вы можете столкнуться с ситуацией, когда вы хотите преобразовать родительский объект в дочерний.

Simple

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

Таким образом, все, что вы делаете, это передаете родительский объект в конструктор дочернего объекта и позволяете конструктору копировать свойства. Вы даже можете фильтровать / изменять их по мере необходимости.

Advanced

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

Это не приведение типов, а то, что вам нужно.

7 голосов
/ 19 августа 2009

Я переделал функцию, опубликованную Джошем (что приведет к ошибке из-за неопределенной переменной $ 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. Кастинг является важной частью объектно-ориентированной жизни, и мне бы хотелось найти лучший способ сделать это, чем уродливые хаки сериализации.

Так что спасибо, Джош!

7 голосов
/ 18 июля 2009

Вот функция для изменения класса объекта:

/**
 * 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

1 голос
/ 03 июня 2017

Если все, что вам нужно, это приведение к типу подсказок, это работает.

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

Да.

1 голос
/ 06 июля 2010

Я думаю, вам нужно набрать приведение, чтобы улучшить IDE. Но сам язык php не нуждается в приведении типов, однако он поддерживает изменения типов во время выполнения для значений в переменных. Посмотрите на автобокс и распаковку. Это то, что по сути делает php. Так что извините, не лучше, чем уже есть IDE.

1 голос
/ 18 июля 2009

Я не верю, что в 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);

Это один из способов справиться с этим.

0 голосов
/ 18 декабря 2018

Даже на 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: синтаксическая ошибка, неожиданный '$ foo' (T_VARIABLE) в xxx.php в строке xxx

То есть вы в принципе не можете этого сделать, но подумайте еще раз: может быть, вы хотели только новую функцию поверх других общедоступных функций класса?

Вы можете сделать это с помощью 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();

Один - паб - один-пар1

Один - паб - один-пар2

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

Вы получите

один

И вы можете получить _protected_in таким же образом

0 голосов
/ 31 июля 2018

Я думаю, что вы имеете в виду Type-Hinting .

Начиная с 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');
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...