С# -подобные методы расширения в PHP?
Мне нравится путь на С#, где вы можете написать метод расширения, а затем выполните следующие действия:
string ourString = "hello";
ourString.MyExtension("another");
или даже
"hello".MyExtention("another");
Есть ли способ иметь подобное поведение в PHP?
Ответы
Ответ 1
Можно, если вы переопределили все свои строки как объекты.
class MyString {
...
function foo () { ... }
}
$str = new MyString('Bar');
$str->foo('baz');
Но вы действительно не хотели бы этого делать. PHP просто не является объектно-ориентированным языком в своем ядре, строки являются просто примитивными типами и не имеют методов.
Синтаксис 'Bar'->foo('baz')
невозможно достичь в PHP без расширения ядра (что не является тем, к чему вы хотите попасть, по крайней мере, не для этой цели:)).
Там также ничего не говорится о расширении функциональности объекта, что делает его по своей сути лучше, чем просто запись новой функции, которая принимает примитив. Другими словами, эквивалент PHP для
"hello".MyExtention("another");
является
my_extension("hello", "another");
Для всех целей и задач он имеет одинаковую функциональность, только другой синтаксис.
Ответ 2
Так как PHP 5.4 содержит признаки, которые могут использоваться как методы расширения.
Пример:
<?php
trait HelloWorld {
public function sayHelloWorld() {
echo 'Hello World';
}
}
class MyHelloWorld {
use HelloWorld;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHelloWorld();
$o->sayExclamationMark();
?>
результат:
Hello World!
После того, как вы включите признак в класс, позвоните ему, например, с именем Extension
, вы можете добавить любые методы, которые вы хотите, и найти их в другом месте. Тогда в этом примере use Extension
становится одноразовым украшением для расширяемых классов.
Ответ 3
Отвлекаясь от проблемы необъектных примитивов в PHP, когда речь идет о реальных классах PHP, если ваша среда является разумной, возможно, вы можете украсить данный класс, чтобы сортировать § эмулировать методы расширения.
Для интерфейса и реализации:
interface FooInterface {
function sayHello();
function sayWorld();
}
class Foo implements FooInterface {
public function sayHello() {
echo 'Hello';
}
public function sayWorld() {
echo 'World';
}
}
До тех пор, пока любые зависимости от Foo
на самом деле зависят от интерфейса FooInterface
(это то, что я подразумеваю под здравомыслящими), вы можете реализовать FooInterface
самостоятельно как оболочку для Foo
, переадресовывать вызовы на Foo
при необходимости и добавьте дополнительные методы по мере необходимости:
class DecoratedFoo implements FooInterface {
private $foo;
public function __construct(FooInterface $foo) {
$this->foo = $foo;
}
public function sayHello() {
$this->foo->sayHello();
}
public function sayWorld() {
$this->foo->sayWorld();
}
public function sayDanke() {
echo 'Danke';
}
public function sayShoen() {
echo 'Shoen';
}
}
Методы sayHello()
и sayWorld()
прошиваются до содержащего объекта Foo
, однако мы добавили sayDanke()
и sayShoen()
.
Следующее:
function acceptsFooInterface(FooInterface $foo) {
$foo->sayHello();
$foo->sayWorld();
}
$foo = new Foo();
acceptsFooInterface($foo);
Работает как ожидалось, давая HelloWorld
; но так же:
$decoratedFoo = new DecoratedFoo($foo);
acceptsFooInterface($decoratedFoo);
$decoratedFoo->sayDanke();
$decoratedFoo->sayShoen();
В результате получается HelloWorldDankeShoen
.
Это ограниченное использование потенциала в шаблоне декоратора; вы можете изменить поведение реализованных методов или просто не перенаправить их вообще (однако мы хотим сохранить предполагаемое поведение по определению исходного класса в этом примере)
Является ли это решением один-к-одному для реализации методов расширения (в соответствии с С#) в PHP? Нет, определенно нет; но расширяемость, предоставляемая этим подходом, поможет решить проблемы более свободно.
§ Фигур, который я подробно изложил на основе чат-беседы по теме: вы никогда не собираетесь копировать его (не сегодня и, вероятно, не tommorow) на PHP, однако ключ в моем Ответ - это шаблоны проектирования. Они предоставляют возможность переносить стратегии с одного языка на другой, когда вы не можете (обычно или когда-либо) портовые функции.
Ответ 4
У меня есть другая реализация для PHP >= 5.3.0 и его, как Decorator, описанная Northborn Design.
Все, что нам нужно, это API для создания расширений и декоратора для применения расширений.
Мы должны помнить, что в методах расширения С# они не разрушают инкапсуляцию расширенного объекта, и они не изменяют объект (для этого нет смысла, вместо этого реализация метода будет более эффективной). И методы расширения являются чисто статическими, и они получают экземпляр объекта, как в примере ниже (С#, из MSDN):
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
Конечно, у нас нет объектов String в PHP, но для всех других объектов мы можем создавать общие декораторы для такого рода волшебства.
Давайте посмотрим на мою реализацию:
API:
<?php
namespace Pattern\Extension;
/**
* API for extension methods in PHP (like C#).
*/
class Extension
{
/**
* Apply extension to an instance.
*
* @param object $instance
* @return \Pattern\Extension\ExtensionWrapper
*/
public function __invoke($instance)
{
return Extension::apply($instance);
}
/**
* Apply extension to an instance.
*
* @param object $instance
* @return \Pattern\Extension\ExtensionWrapper
*/
public static function apply($instance)
{
return new ExtensionWrapper($instance, \get_called_class());
}
/**
* @param mixed $instance
* @return boolean
*/
public static function isExtensible($instance)
{
return ($instance instanceof Extensible);
}
}
?>
Декоратор:
<?php
namespace Pattern\Extension;
/**
* Demarcate decorators that resolve the extension.
*/
interface Extensible
{
/**
* Verify the instance of the holded object.
*
* @param string $className
* @return bool true if the instance is of the type $className, false otherwise.
*/
public function holdsInstanceOf($className);
/**
* Returns the wrapped object.
* If the wrapped object is a Extensible the returns the unwrap of it and so on.
*
* @return mixed
*/
public function unwrap();
/**
* Magic method for the extension methods.
*
* @param string $name
* @param array $args
* @return mixed
*/
public function __call($name, array $args);
}
?>
И общая реализация:
<?php
namespace Pattern\Extension;
/**
* Generic version for the Extensible Interface.
*/
final class ExtensionWrapper implements Extensible
{
/**
* @var mixed
*/
private $that;
/**
* @var Extension
*/
private $extension;
/**
* @param object $instance
* @param string | Extension $extensionClass
* @throws \InvalidArgumentException
*/
public function __construct($instance, $extensionClass)
{
if (!\is_object($instance)) {
throw new \InvalidArgumentException('ExtensionWrapper works only with objects.');
}
$this->that = $instance;
$this->extension = $extensionClass;
}
/**
* {@inheritDoc}
* @see \Pattern\Extension\Extensible::__call()
*/
public function __call($name, array $args)
{
$call = null;
if (\method_exists($this->extension, '_'.$name)) {
// this is for abstract default interface implementation
\array_unshift($args, $this->unwrap());
$call = array($this->extension, '_'.$name);
} elseif (\method_exists($this->extension, $name)) {
// this is for real implementations
\array_unshift($args, $this->unwrap());
$call = array($this->extension, $name);
} else {
// this is for real call on object
$call = array($this->that, $name);
}
return \call_user_func_array($call, $args);
}
/**
* {@inheritDoc}
* @see \Pattern\Extension\Extensible::unwrap()
*/
public function unwrap()
{
return (Extension::isExtensible($this->that) ? $this->that->unwrap() : $this->that);
}
/**
* {@inheritDoc}
* @see \Pattern\Extension\Extensible::holdsInstanceof()
*/
public function holdsInstanceOf($className)
{
return \is_a($this->unwrap(), $className);
}
}
?>
Использование:
Предположим, что существует сторонний класс:
class ThirdPartyHello
{
public function sayHello()
{
return "Hello";
}
}
Создайте расширение:
use Pattern\Extension\Extension;
class HelloWorldExtension extends Extension
{
public static function sayHelloWorld(ThirdPartyHello $that)
{
return $that->sayHello().' World!';
}
}
Плюс: для любителей интерфейса создайте абстрактное расширение:
<?php
interface HelloInterfaceExtension
{
public function sayHelloFromInterface();
}
?>
<?php
use Pattern\Extension\Extension;
abstract class AbstractHelloExtension extends Extension implements HelloInterfaceExtension
{
public static function _sayHelloFromInterface(ThirdPartyOrLegacyClass $that)
{
return $that->sayHello(). ' from Hello Interface';
}
}
?>
Затем используйте его:
////////////////////////////
// You can hide this snippet in a Dependency Injection method
$thatClass = new ThirdPartyHello();
/** @var ThirdPartyHello|HelloWorldExtension $extension */
$extension = HelloWorldExtension::apply($thatClass);
//////////////////////////////////////////
$extension->sayHello(); // returns 'Hello'
$extension->sayHelloWorld(); // returns 'Hello World!'
//////////////////////////////////////////
// Abstract extension
$thatClass = new ThirdPartyHello();
/** @var ThirdPartyHello|HelloInterfaceExtension $extension */
$extension = AbstractHelloExtension::apply($instance);
$extension->sayHello(); // returns 'Hello'
$extension->sayHelloFromInterface(); // returns 'Hello from Hello Interface'
Плюсы:
- Очень похожий способ расширений С# в PHP;
- Невозможно напрямую проверить экземпляр расширения как экземпляр расширенного объекта, но это хорошо, потому что он более безопасен, потому что у нас могут быть места, где экземпляр этой классы не расширяется;
- Как цель рамки для повышения гибкости команды, вы должны писать меньше;
- Использование расширений, по-видимому, является частью объекта, но его просто украшено (возможно, это интересно командам, которые быстро развиваются, но в будущем будут рассматривать реализацию этого расширенного объекта, если задействовано наследие);
- Вы можете использовать статический метод расширения непосредственно для повышения производительности, но при этом теряете способность имитировать части вашего кода (DI очень указан).
Минусы:
- Расширение должно быть объявлено объекту. Его не просто как импорт, как на С#, вы должны "украсить" желаемый экземпляр, чтобы дать ему расширение.
- Невозможно напрямую проверить экземпляр расширения как экземпляр расширенного объекта, больше кода для тестирования с использованием API;
- Недостатки производительности из-за использования магических методов (но когда требуется производительность, мы меняем язык, воссоздаем ядро, используем минималистские рамки, ассемблер, если это необходимо);
Здесь Gist для этого Api: https://gist.github.com/tennaito/9ab4331a4b837f836ccdee78ba58dff8