Ссылка на позднюю статическую привязку PHP

Ситуация

В этом веб-приложении, которое я создаю, есть последовательность «начальной загрузки», которая определяет (через константы) и инициирует расширенный контроллер. В настоящее время контроллер отслеживает активы (файлы сценариев, css и т. д.), которые будут развернуты на более позднем этапе рендеринга, с помощью ряда статических переменных. Я упрощу код здесь, думайте о нем как о псевдо-PHP.

/* CONTROLLER CLASS */
class Controller {
    protected static $aryScriptFiles = array();
    public function __construct() {
        /* Behaviour */
        /* Some logic that identifies/calls Home_Controller method Index */
    }
    public static function Add_Script($strFileName) {
        static::$aryScriptFiles[] = $strFileName;
    }    
}

/* HOME_CONTROLLER CLASS */
class Home_Controller extends Controller {
    protected static $aryScriptFiles = array('default', 'carousel', 'etc');
    protected function Index() {
        /* Behaviour */
        /* Load the view as an include. It is "part" of the User_Controller */ 
    }
}

/* EXAMPLE_HELPER */
class Example_Helper {
    public static function Test() {

         /* THE NEXT LINE IS IMPORTANT FOR THE QUESTION */
         $objController = CONTROLLER;
         $objController::Add_Script('dominoes');
    }    
}

/* INDEX VIEW FILE */
<h1>Welcome!</h1>
<?php
echo get_class(); <-- Would echo 'User_Controller'
Example_Helper::Test();

/* Simplification of render process */
foreach(static::$aryScriptFiles as $strFileName) {
    /* Render the HTML script tag */
}
?>

Поток

Хорошо, учитывая вышесказанное, есть загрузчик, который в конечном итоге вызывает User_Controller. Для примера я просто определил их, чтобы вы знали, в каком состоянии будет следовать сценарий.

$strControllerName = 'User_Controller';
define('CONTROLLER', $strControllerName);
$objController = new $strControllerName();

В итоге вы получите массив aryScriptFiles, содержащий 4 записи, и это прекрасно работает.

Проблема

Прежде чем читать дальше, обратите внимание, что я не хочу использовать магические методы, глобальные переменные или передавать ссылку на имя контроллера в функцию Helper.

Я хотел бы попытаться удалить строку в вспомогательном файле, которая вытягивает текущее имя контроллера в переменную из константы.

$objController = CONTROLLER; <-- I want this to shoo shoo

Если бы я просто попытался использовать следующее, файл сценария, который добавляется с помощью помощника, является частью исходного массива контроллеров, а не контроллера Home.

Controller::Add_Script('dominoes'); <-- Will not be part of the Home_Controller array

Вопрос

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

  • Держите файл просмотра ОЧЕНЬ простым
  • Сохраняйте вспомогательные файлы простыми.
  • Избегайте необходимости добавлять какой-либо код в Home_Controller сверх необходимого.

В настоящее время я думаю, что одним из лучших вариантов было бы размещение «активов» в отдельном классе, просто хочу знать, возможно ли это.

Спасибо за чтение.


person Simon    schedule 09.07.2014    source источник
comment
Конструктор базового класса вызывает что-то в дочернем классе? Это довольно неортодоксально.   -  person Ja͢ck    schedule 09.07.2014
comment
Я собираюсь оставить этот вопрос открытым на день или два, потому что я собираюсь использовать менеджер активов, но мне было бы ОЧЕНЬ интересно посмотреть, сможет ли кто-нибудь придумать метод, который позволит мне сохранить код как в представлении, так и в помощнике максимально понятен.   -  person Simon    schedule 09.07.2014


Ответы (2)


Прежде всего, подумайте о разделении интересов. Должен ли контроллер действительно нести ответственность за управление активами?. Почему вы вообще сделали метод добавления ресурсов статическим?

Я не хочу использовать магические методы, глобальные переменные или передавать ссылку на имя контроллера в функцию Helper.

Чего вы ожидаете? Если вы пытаетесь заставить класс зависеть от другого класса в совершенно другой области и контексте, ваш единственный вариант — использовать уродливые хаки, чтобы сделать ваш объект глобально доступным.

Внедрение зависимостей на помощь

Зачем вашему хелперу знать о том, какой контроллер и как контроллер обрабатывается извне?

Единственное, что должен делать ваш помощник, это работать с контроллером (в вашем случае). Он не должен пытаться волшебным образом определить, какой контроллер используется. Он должен просто взять контроллер и работать с ним.

class Example_Helper {
    public static function Test($controller) {
         $controller::Add_Script('dominoes');
    }    
}

Example_Helper::Test($objController);

Поскольку метод addScript() и свойство $aryScriptFiles в любом случае являются статическими, вы также можете просто вызвать метод в помощнике на родительском контроллере. Это не имело бы никакого значения.

Кроме того, почему вы хотите разговаривать с вашим контроллером из представления? Представление должно быть «тупым», оно не должно иметь возможности хранить данные и работать с ними, кроме тех, которые были переданы ему контроллером.

Не было бы более разумным добавить функциональность в ваш контроллер или одну из его служб, которая передает необходимые активы в ваше представление, вместо того, чтобы заставлять представление получать данные из контроллера само по себе?

Я думаю, что в вашем коде есть несколько логических ошибок. Особенно ваше использование статических свойств и методов. Если бы вы могли немного пояснить это, я мог бы немного остановиться на деталях.

person thpl    schedule 09.07.2014
comment
Предлагая внедрение зависимостей для SoC, с точки зрения сопровождающих, это будет лучший подход. В качестве эксперимента я хотел бы попытаться избежать необходимости передавать что-либо, кроме того, что непосредственно связано с помощниками. В этом случае представление каким-то образом связано с контроллером, потому что помощник предназначен исключительно для представления. т.е. Загрузите карусель и отсортируйте все дополнительные сценарии, которые нам нужно включить. - person Simon; 09.07.2014
comment
Пометка этого ответа как правильного, как предложение внедрения зависимостей, идеально подходит для удобства обслуживания, даже если это означает передачу этого дополнительного параметра. - person Simon; 11.07.2014

Помимо архитектурных соображений (активы действительно должны управляться отдельным AssetManager), ваша проблема может быть относительно легко решена благодаря довольно особой собственной архитектуре PHP, которая раскрывается с помощью таких методов, как get_called_class. Это позволяет вам писать такой код:

$assets = [];  // Global for brevity of example

class Base {
  static function addScript($script)
  {
    global $assets;

    $myName = get_called_class();
    $assets[$myName][] = $script;
  }
}

class Derived extends Base {
  public function __construct()
  {
    self::addScript('test');
  }
}

$foo = new Derived();
var_dump($assets);

Который выведет следующее:

array(1) {
  ["Derived"]=>
  array(1) {
    [0]=>
    string(4) "test"
  }
}

Обратите внимание, что использование get_class вместо get_called_class здесь покажет имя массива как Base вместо Derived, тогда как Derived — это то, что вам нужно. Таким образом, вы можете встроить вспомогательные функции в Controller, которые автоматически получают имя класса и передают его центральному менеджеру ресурсов.

person Niels Keurentjes    schedule 09.07.2014