Использование селектора 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;
}
Лучше всего использовать магический метод __ 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, добавьте пример с возвращаемым значением.
Единственный способ сделать это - использовать магию __call
. Вы должны сделать все все методы частными, чтобы они не были доступны извне. Затем определите метод __call для обработки вызовов метода. В __call вы можете выполнить любую функцию, которую хотите , прежде чем вызовет функцию, которая была намеренно вызвана.
Возможно, это немного устарело, но вот приходят мои 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 с частотой, возможно, это проблема.
Возможно, лучшим способом до сих пор является создание собственного вызывающего метода и обтекание всего, что вам нужно до и после метода:
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 все еще автозаполняла методы, которые, скорее всего, не произойдут, если вы объявите методы как частные.
Все предыдущие попытки в основном изъяны из-за 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 решения, которые, как я думаю, могут решить вашу проблему более чистым способом:
$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
, как вам нравится. $callOnMethod
следует применять только для определенных экземпляров, тогда AOP не то, что вы ищете. <?php
class test
{
public function __call($name, $arguments)
{
$this->test1(); // Call from here
return call_user_func_array(array($this, $name), $arguments);
}
// methods here...
}
?>
Попробуйте добавить этот метод, переопределяющий в классе ...
Давайте поговорим об этом:
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'));
То, что я сделал бы
Если вы действительно, действительно смелый, вы можете сделать это с расширением runkit. ( http://www.php.net/manual/en/book.runkit.php [/ д0]). Вы можете играть с runkit_method_redefine (вам может понадобиться Reflection также для извлечения определения метода), или, может быть, комбинация runkit_method_rename (старая функция) / runkit_method_add (новая функция, которая обертывает вызовы вашей функции test1 и старую функцию)
_
в примере и сделалtest3
protected
. – Kristoffer Sall-Storgaard 8 February 2013 в 01:00return
в__call
– Alex2php 4 April 2015 в 16:39test()
в каждом методе. – RN Kushwaha 24 April 2015 в 07:24__callStatic
– Kristoffer Sall-Storgaard 4 July 2017 в 12:40