Подтвердить что ты не робот

С# -подобные методы расширения в PHP?

Мне нравится путь на С#, где вы можете написать метод расширения, а затем выполните следующие действия:

string ourString = "hello";
ourString.MyExtension("another");

или даже

"hello".MyExtention("another");

Есть ли способ иметь подобное поведение в PHP?

4b9b3361

Ответ 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