Приведение типов для пользовательских объектов
Так же, как мы делаем с __ToString, есть ли способ определить метод для приведения?
$obj = (MyClass) $another_class_obj;
12 ответов
Нет необходимости печатать cast в 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, это не только невозможно, но и бессмысленно спрашивать в первую очередь.
Хотя в 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;
}
}
}
Это не приведение типов, но оно делает то, что вам нужно.
Вот функция для изменения класса объекта:
/**
* 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
Я переделал функцию, опубликованную Джошем (что приведет к ошибке из-за неопределенной переменной $ 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. Кастинг - важная часть объектно-ориентированной жизни, и мне бы хотелось найти лучший способ сделать это, чем уродливые хаки сериализации.
Так что спасибо, Джош!
Если приведение к типу хинтинга - это все, что вам нужно, это работает.
if( is_object($dum_class_u_want) && $dum_class_u_want instanceof ClassYouWant )
{
// type hints working now
$dum_class_u_want->is_smart_now();
}
Ага.
Я не верю, что в 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);
Это один из способов справиться с этим.
Я думаю, что вам нужно набрать приведение, чтобы сделать IDE лучше. Но сам язык php не нуждается в приведении типов, однако он поддерживает изменения типов во время выполнения для значений в переменных. Посмотрите на автобокс и распаковку. Это то, что по сути делает php. Так что извините, не лучше, чем уже есть IDE.
В основном мне нужно приведение типов, чтобы включить intellisense, поэтому я просто создаю помощник приведения типов:
function castToTest($val): Test
{
return $val;
}
$test = castToTest(require("someFile.php"));
Возврат из файла выглядит некрасиво и не позволяет использовать подсказки типа, так что это прекрасный пример того, как можно достичь intellisense с помощью помощника приведения типов.
Вы можете создать два объекта отражения, а затем скопировать значения в другой пример, который можно найти на 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;
}
Даже на 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();
Один - паб - один пар
Один - паб - один-пар2
Обертка - новая функция
Что делать, если вы хотите получить защищенную собственность?
Вы тоже можете это сделать
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);
Ты получишь
один
И вы можете get_protected_in таким же образом
Я думаю, что вы имеете в виду 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');
Ответ, который был выбран как правильный, на самом деле является неправильным ответом (или вопрос неверно истолкован).
Правильная интерпретация вопроса связана с основным принципом парадигмы объектно-ориентированного проектирования, согласно которому дочерний класс должен иметь возможность присваивать значение переменной своего родительского класса или переменной интерфейса, который реализуется учебный класс.
Поскольку это основа объектно-ориентированного проектирования, нельзя сказать, что это необходимо для языка-A, но не требуется для языка-B.
Правильный ответ заключается в том, что на сегодняшний день в PHP отсутствуют основные требования объектно-ориентированного проектирования.
Чтобы код соответствовал A) принципу инверсии зависимостей (DIP) и B) принципу разделения интерфейса («I» в SOLID)
... PHP должен позволять указывать тип переменной с именем интерфейса, который реализует конкретный класс, чтобы можно было использовать только функциональные возможности, специфичные для интерфейса класса реализации (обслуживания).
Это основной принцип объектно-ориентированного проектирования, и без возможности объявлять тип переменной определенного типа кода интерфейса не может соответствовать (и не может пользоваться преимуществами) принципа DIP и разделения интерфейса.
Кроме того, это динамическое связывание всегда выполняется во время выполнения, а не во время компиляции, поэтому PHP не должен был столкнуться с проблемами при обеспечении этого важного элемента объектно-ориентированной парадигмы.