привязать обратный вызов к началу и концу метода [duplicate]

Использование селектора sibling - это общее решение для стилизации других элементов при наведении курсора на заданный, но оно работает , только если другие элементы следуют за данным в DOM . Что мы можем сделать, когда другие элементы должны быть на самом деле до зависания? Предположим, что мы хотим реализовать виджет оценки уровня сигнала, подобный приведенному ниже:

Это можно легко сделать с использованием модели flexbox CSS, установив flex-direction - reverse, так что элементы отображаются в противоположном порядке от того, который находится в DOM.

Flexbox очень хорошо поддерживается на 95% современных браузеров.

.rating {
  display: flex;
  flex-direction: row-reverse;
  width: 9rem;
}
.rating div {
  flex: 1;
  align-self: flex-end;
  background-color: black;
  border: 0.1rem solid white;
}
.rating div:hover {
  background-color: lightblue;
}
.rating div[data-rating="1"] {
  height: 5rem;
}
.rating div[data-rating="2"] {
  height: 4rem;
}
.rating div[data-rating="3"] {
  height: 3rem;
}
.rating div[data-rating="4"] {
  height: 2rem;
}
.rating div[data-rating="5"] {
  height: 1rem;
}
.rating div:hover ~ div {
  background-color: lightblue;
}

48
задан Kristoffer Sall-Storgaard 8 February 2013 в 01:02
поделиться

8 ответов

Лучше всего использовать магический метод __ call , см. ниже:

<?php

class test {
    function __construct(){}

    private function test1(){
        echo "In test1", PHP_EOL;
    }
    private function test2(){
        echo "test2", PHP_EOL;
    }
    protected function test3(){
        return "test3" . PHP_EOL;
    }
    public function __call($method,$arguments) {
        if(method_exists($this, $method)) {
            $this->test1();
            return call_user_func_array(array($this,$method),$arguments);
        }
    }
}

$a = new test;
$a->test2();
echo $a->test3();
/*
* Output:
* In test1
* test2
* In test1
* test3
*/

Обратите внимание, что test2 и test3 не отображаются в контексте где они вызываются из-за protected и private. Если методы общедоступны, приведенный выше пример не сработает.

test1 не нужно объявлять private.

Пример ideone.com можно найти здесь

Обновлено: добавьте ссылку на ideone, добавьте пример с возвращаемым значением.

52
ответ дан Kristoffer Sall-Storgaard 17 August 2018 в 14:25
поделиться
  • 1
    На самом деле, а) вы можете использовать методы также как защищенные, просто не публичные, и б) вам не понадобится _ – ktolis 8 February 2013 в 00:42
  • 2
    Вы правы, защита будет работать, я удалил _ в примере и сделал test3 protected. – Kristoffer Sall-Storgaard 8 February 2013 в 01:00
  • 3
    Если return в __call – Alex2php 4 April 2015 в 16:39
  • 4
    С помощью этого решения вам по-прежнему необходимо модифицировать общедоступные методы. Не имеет смысла, что вы можете модифицировать модификаторы доступа к вашему методу, но не можете вызвать ваш желаемый метод test() в каждом методе. – RN Kushwaha 24 April 2015 в 07:24
  • 5
    @Nebulosar вы можете построить что-то подобное с помощью __callStatic – Kristoffer Sall-Storgaard 4 July 2017 в 12:40

Единственный способ сделать это - использовать магию __call . Вы должны сделать все все методы частными, чтобы они не были доступны извне. Затем определите метод __call для обработки вызовов метода. В __call вы можете выполнить любую функцию, которую хотите , прежде чем вызовет функцию, которая была намеренно вызвана.

0
ответ дан halfdan 17 August 2018 в 14:25
поделиться

Возможно, это немного устарело, но вот приходят мои 2 цента ...

Не думаю, что предоставление доступа к приватным методам через __call () - хорошая идея. Если у вас есть метод, который вы действительно не хотите вызывать вне своего объекта, у вас нет способа избежать этого.

Я думаю, что еще одним элегантным решением должно быть создание какого-то универсального прокси-сервера / decorator и с помощью __call () внутри него. Позвольте мне показать, как:

class Proxy
{
    private $proxifiedClass;

    function __construct($proxifiedClass)
    {
        $this->proxifiedClass = $proxifiedClass;
    }

    public function __call($methodName, $arguments)
    {

        if (is_callable(
                array($this->proxifiedClass, $methodName)))
        {
            doSomethingBeforeCall();

            call_user_func(array($this->proxifiedClass, $methodName), $arguments);

            doSomethingAfterCall();
        }
        else
        {
            $class = get_class($this->proxifiedClass);
            throw new \BadMethodCallException("No callable method $methodName at $class class");
        }
    }

    private function doSomethingBeforeCall()
    {
        echo 'Before call';
        //code here
    }

    private function doSomethingAfterCall()
    {
        echo 'After call';
        //code here
    }
}

Теперь просто тестовый класс:

class Test
{
    public function methodOne()
    {
        echo 'Method one';
    }

    public function methodTwo()
    {
        echo 'Method two';
    }

    private function methodThree()
    {
        echo 'Method three';
    }

}

И все, что вам нужно сделать, это:

$obj = new Proxy(new Test());

$obj->methodOne();
$obj->methodTwo();
$obj->methodThree(); // This will fail, methodThree is private

Преимущества:

1) Вам просто нужен один прокси-класс, и он будет работать со всеми вашими объектами. 2) Вы не будете неуважительно относиться к правилам доступности. 3) Вам не нужно менять проксифицированные объекты.

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

7
ответ дан marcellorvalle 17 August 2018 в 14:25
поделиться

Возможно, лучшим способом до сих пор является создание собственного вызывающего метода и обтекание всего, что вам нужно до и после метода:

class MyClass {

    public function callMethod()
    {
        $args = func_get_args();

        if (count($args) == 0) {
            echo __FUNCTION__ . ': No method specified!' . PHP_EOL . PHP_EOL;;
        } else {
            $method = array_shift($args); // first argument is the method name and we won't need to pass it further
            if (method_exists($this, $method)) {
                echo __FUNCTION__ . ': I will execute this line and then call ' . __CLASS__ . '->' . $method . '()' . PHP_EOL;
                call_user_func_array([$this, $method], $args);
                echo __FUNCTION__ . ": I'm done with " . __CLASS__ . '->' . $method . '() and now I execute this line ' . PHP_EOL . PHP_EOL;
            } else
                echo __FUNCTION__ . ': Method ' . __CLASS__ . '->' . $method . '() does not exist' . PHP_EOL . PHP_EOL;
        }
    }

    public function functionAA()
    {
        echo __FUNCTION__ . ": I've been called" . PHP_EOL;
    }

    public function functionBB($a, $b, $c)
    {
        echo __FUNCTION__ . ": I've been called with these arguments (" . $a . ', ' . $b . ', ' . $c . ')' . PHP_EOL;
    }
}

$myClass = new MyClass();

$myClass->callMethod('functionAA');
$myClass->callMethod('functionBB', 1, 2, 3);
$myClass->callMethod('functionCC');
$myClass->callMethod();

И вот вывод:

callMethod: I will execute this line and then call MyClass->functionAA()
functionAA: I've been called
callMethod: I'm done with MyClass->functionAA() and now I execute this line 

callMethod: I will execute this line and then call MyClass->functionBB()
functionBB: I've been called with these arguments (1, 2, 3)
callMethod: I'm done with MyClass->functionBB() and now I execute this line 

callMethod: Method MyClass->functionCC() does not exist

callMethod: No method specified!

Вы даже можете пойти дальше и создать белый список методов, но я оставляю его таким образом для более простого примера.

Вы больше не будете вынуждены делать методы частными и использовать их через __call (). Я предполагаю, что могут быть ситуации, когда вы захотите вызвать методы без оболочки или вы хотите, чтобы ваша среда IDE все еще автозаполняла методы, которые, скорее всего, не произойдут, если вы объявите методы как частные.

3
ответ дан NTT 17 August 2018 в 14:25
поделиться

Все предыдущие попытки в основном изъяны из-за http://ocramius.github.io/presentations/proxy-pattern-in-php/#/71

Вот простой пример, взятый из моих слайдов:

class BankAccount { /* ... */ }

И вот наша «бедная» логика перехватчика:

class PoorProxy {
    public function __construct($wrapped) {
        $this->wrapped = $wrapped;
    }

    public function __call($method, $args) {
        return call_user_func_array(
            $this->wrapped,
            $args
        );
    }
}

Теперь, если мы вызываем следующий метод:

function pay(BankAccount $account) { /* ... */ }

Тогда это не сработает:

$account = new PoorProxy(new BankAccount());

pay($account); // KABOOM!

Это относится ко всем решениям, предлагающим реализовать «прокси».

Решения, предлагающие явное использование другие методы, которые затем вызывают ваш внутренний API, являются ошибочными, поскольку они заставляют вас изменять свой общедоступный API для изменения внутреннего поведения и уменьшают безопасность типов.

Решение, предоставленное Kristoffer, не учитывает public, что также является проблемой, так как вы не можете переписать свой API, чтобы сделать все private или protected.

Вот решение, которое частично решает эту проблему:

class BankAccountProxy extends BankAccount {
    public function __construct($wrapped) {
        $this->wrapped = $wrapped;
    }

    public function doThings() { // inherited public method
        $this->doOtherThingsOnMethodCall();

        return $this->wrapped->doThings();
    }

    private function doOtherThingsOnMethodCall() { /**/ }
}

Вот как вы его используете:

$account = new BankAccountProxy(new BankAccount());

pay($account); // WORKS!

Это безопасное по типу, чистое решение, но оно включает

Написание этого кода шаблона НЕ весело, поэтому вы можете использовать разные подходы.

Чтобы дать вам идею насколько сложна эта категория проблем, я могу просто сказать вам, что я написал целую библиотеку , чтобы решить их, а некоторые более умные, более мудрые, пожилые люди даже пошли и изобрели совершенно другую парадигму, называемую «Аспектно-ориентированное программирование» (AOP) .

Поэтому я предлагаю вам изучить эти 3 решения, которые, как я думаю, могут решить вашу проблему более чистым способом:

  • Используйте ProxyManager 's access interceptor ", который в основном является прокси-типом, который позволяет запускать закрытие при вызове других методов ( пример ). Вот пример того, как проксировать ВСЕ вызовы публичного API $object:
    use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
    
    function build_wrapper($object, callable $callOnMethod) {
        return (new AccessInterceptorValueHolderFactory)
            ->createProxy(
                $object,
                array_map(
                    function () use ($callOnMethod) {
                        return $callOnMethod;
                    },
                    (new ReflectionClass($object))
                        ->getMethods(ReflectionMethod::IS_PUBLIC)
                )
            ); 
    }
    
    , а затем просто использовать build_wrapper, как вам нравится.
  • Использовать GO-AOP-PHP , которая является реальной библиотекой AOP, полностью написанной на PHP, но будет применять эту логику для ВСЕХ экземпляров классов, для которых вы определяете сокращения точек. Это может быть или не быть тем, что вы хотите, и если ваш $callOnMethod следует применять только для определенных экземпляров, тогда AOP не то, что вы ищете.
  • Используйте расширение g7] PHP AOP , который, как я считаю, не является хорошим решением, главным образом потому, что GO-AOP-PHP решает эту проблему более элегантно / отлаживаемо, а потому, что расширения в PHP по сути являются беспорядком (что должно приписываться внутренним функциям PHP, а не разработчикам расширений). Кроме того, используя расширение, вы делаете свое приложение как можно более переносимым (попробуйте убедить sysadmin установить скомпилированную версию PHP, если вы посмеете), и вы не можете использовать свое приложение для новых новых движков, таких как HHVM.
18
ответ дан Ocramius 17 August 2018 в 14:25
поделиться
<?php

class test
{

    public function __call($name, $arguments)
    {
        $this->test1(); // Call from here
        return call_user_func_array(array($this, $name), $arguments);
    }

    // methods here...

}

?>

Попробуйте добавить этот метод, переопределяющий в классе ...

3
ответ дан Otar 17 August 2018 в 14:25
поделиться
  • 1
  • 2
    +1, поскольку это лучший способ сделать это, не нарушая принципов ООП, указав публичные методы как частные, хотя они все еще доступны как общедоступные. – poke 15 September 2010 в 11:37
  • 3
    @Yogesh, см. Обновленный ответ, пожалуйста ... – Otar 15 September 2010 в 11:45

Давайте поговорим об этом:

class test
{
    function __construct()
    {

    }

    private function test1()
    {
        echo "In test1";
    }

    private function test2()
    {
        echo "test2";
    }

    private function test3()
    {
        echo "test3";
    }

    function CallMethodsAfterOne($methods = array())
    {
        //Calls the private method internally
        foreach($methods as $method => $arguments)
        {
            $this->test1();
            $arguments = $arguments ? $arguments : array(); //Check
            call_user_func_array(array($this,$method),$arguments);
        }
    }
}

$test = new test;
$test->CallMethodsAfterOne('test2','test3','test4' => array('first_param'));

То, что я сделал бы

-2
ответ дан RobertPitt 17 August 2018 в 14:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: