Namespace names PHP and php, and compound names starting with these names (like PHP\Classes) are reserved for internal language use and should not be used in the userspace code.
Сервисы и репозитории в принципе не могут хранить состояние, поэтому в полях класса хранить его нельзя. Оформить в виде класса то, что можно оформить в виде замыкания — громоздко и вряд-ли более понятно. Куча мелких классов-адаптеров — куча мелких файлов в ФС. Передавать аргументами — требует идентичности интерфейсов, а соответственно изменения классов или наследования.
если проектировать не предполагая использование замыканий, то задачу можно решить и без них
Да, вот только зачем отвергать решения, которые позволяют решить задачу лучше и проще? Здесь как раз замыкания вполне соответствуют KISS. В любой точке, в которой возможна передача коллбэка, можно, а зачастую и нужно, использовать замыкание, чтобы не плодить методы/наследники-адаптеры.
Тогда подкрепите свои слова примером того, как можно переписать это без замыканий, не сохраняя состояние в сервисе или репозитории (это идеологически и архитектурно неправильно). Я вот утверждаю, что некоторые куски кода короче, элегантнее и понятнее (выберите любые два :) с использованием замыканий.
У замыканий есть такое преимущество, как возможность легковесного хранения состояния. Рассмотрим пример в котором имеются:
1. Порционный итератор (получает абстрактный загрузчик данных, передает ему границы порции и получает в ответ данные)
2. Репозиторий сущностей, который не хранит состояние и его метод для получения данных, который требует множество параметров, кроме возможных границ порции.
3. Сервис, который использует доступ к репозиторию с помощью порционного итератора, который тоже не хранит состояние
Используя замыкание мы можем создать легковесный адаптер, не внося изменений в репозиторий, итератор или сервис:
public function getActualScheduleItems(DateTime $date = null, DateTimeZone $time_zone = null)
{
$data_loader = function($offset, $limit) use ($date, $time_zone)
{
return $this->getRepo()->findActualPeriodicalSchedules($date, $time_zone, $offset, $limit);
};
return $this->createPortionIterator($data_loader);
}
Вот вам еще в копилку, из реального проекта, обертка для Doctrine DQL ExpressionBuilder:
Немного магии замыканий
/**
* Метод возвращает набор лямбда-функций
* для удобного доступа к выражениями Doctrine DQL.
*
* @example
*
* <code>
* $query_builder = $this->createSelect();
*
* // импортируем все лямбда-функции в текущую область видимости
* extract($this->getExpressions());
*
* $query_builder->where
* (
* // построим WHERE с помощью лямбда-функций
* $andx
* (
* $eq($alias('user'), ':user'),
* $eq($alias('type'), ':type'),
* $in($alias('status'), array(':active', ':disabled'))
* )
* );
* </code>
*
* @return array
*/
public function getExpressions()
{
if (!empty (static::$expressions))
{
return static::$expressions;
}
$expr_builder = $this->getExpressionBuilder();
// :TRICKY: Imenem 22.03.12
//
// Эта лямбда-функция создает прокси
// к методу объекта ExpressionsBuilder.
// После ее вызова с указанием имени метода
// будет создана лямбда-функция,
// которая вызывает одноименный метод ExpressionsBuilder,
// передавая ему полученные параметры.
$expr_proxy = function($method) use ($expr_builder)
{
return function() use ($method, $expr_builder)
{
return call_user_func_array(array($expr_builder, $method), func_get_args());
};
};
$alias = static::$alias;
static::$expressions = array
(
'alias' => function($field) use ($alias)
{
return $alias . '.' . $field;
},
'andx' => $expr_proxy('andx'),
'orx' => $expr_proxy('orx'),
'eq' => $expr_proxy('eq'),
'neq' => $expr_proxy('neq'),
'gte' => $expr_proxy('gte'),
'lte' => $expr_proxy('lte'),
'in' => $expr_proxy('in'),
'notIn' => $expr_proxy('notIn'),
'between' => $expr_proxy('between'),
'not' => $expr_proxy('not'),
'exists' => $expr_proxy('exists')
);
return static::$expressions;
}
Пример DQL-запроса
/**
* Метод возвращает актуальные на текущий день
* запланированные платежи по уровню баланса
*
* @param DateTime $date Метка времени запуска
* @param int $offset Индекс первого элемента, который должен попасть в выборку
* @param int $limit Максимальное кол-во элементов в выборке
*
* @return Acme\SomeBundle\Entity\ScheduleItem[] Массив запланированных задач
*/
public function findActualBalanceSchedules(DateTime $date = null, $offset = null, $limit = null)
{
extract($this->getExpressions());
$pt_repo = $this->getRepo('PaymentTask');
// получим лямбду, которая добавляет
// алиас сущности PaymentTask к имени поля
$pt_alias = $pt_repo->getExpressions()['alias'];
// создадим подзапрос, который выберет
// для каждой ScheduleItem все PaymentTask,
// которые были созданы не позднее суток назад,
// и не были до конца выполнены
$created_payment_tasks = $pt_repo
->createSelect()
->select($pt_alias('id'))
->andWhere
(
$gte($pt_alias('started'), ':started'),
$in($pt_alias('status'), PaymentTask::getActiveStatusSet())
);
return $this
->createActualSelect(ScheduleItem::TYPE_BALANCE, $offset, $limit)
->andWhere
(
// выберем все запланированные задачи по балансу,
// для которых не существует неоконченных платежных задач
$not($exists($created_payment_tasks))
)
// выберем задачи, созданные не позднее суток назад
->setParameter('started', (new DateTime)->sub(new DateInterval('P1D')))
->getQuery()
->getResult();
}
Сервис не должен ничего знать ни о контейнере, ни о геттерах для получения сервисов. У него должен быть интерфейс для внедрения зависимости, а в настройках контейнера вы уже описываете, что передать сервису. Есть еще несколько способов внедрения зависимостей, которые реализованы в Symfony 2, почитать можно здесь и здесь. А в документации можно посмотреть, что еще позволяет сделать контейнер в Symfony 2. А $mailer = $this->container->get('mailer'); это действительно костыль и плохой тон.
Вопрос все-таки был «почему все финальное, особенно конструктор». Сеттеры и цепочки методов это конечно хорошо, DI-контейнеры позволяют описать инициализацию один раз и забить, но все-равно, я не вижу смысла лишать этот класс возможности перегрузки методов.
А почему все методы объявлены финальными? А если я хочу, например, изменить папку, в которую пишутся логи или имена файлов логов? Или вместо echo «Starting...\n»; пробросить в конструктор логгер и сделать $this->logger->log(«Starting...\n»);?
Молекула водорода — 2 протона, 2 электрона. Атом гелия — 2 протона, 2 нейтрона, 2 электрона. Что из них больше? Приведите ссылку на материал, описывающий проницаемость водорода и гелия через различные материалы.
А еще — постоянный выхлоп радиоактивных благородных газов в атмосферу. Есть неплохая статья на эту тему — Ядерная мифология конца XX века, а еще есть серия брошюр, где каждый параграф статьи расписан намного подробнее.
Пытался пройти 10 километров, поджав одну ногу, на костылях. Шел неделю. Потом бросил костыли, одел кроссовки и не напрягаясь дошел за 2 часа. Идти стало гораааздо легче.
Уже много раз говорили, что фремворки нельзя сравнивать с чистым языком.
И первый и второй варианты вызывают приведение типа, а значит не являются абсолютно прозрачными. Классический пример:
if ($position = strpos('123', '1'))
{
print_r('Символ найден на позиции ' . $position);
print "\n";
}
else
{
print_r('Символ не найден');
print "\n";
}
imenem@localhost:~
$ test
Символ не найден
Вы можете парировать фразой «я всегда знаю, как результат выполнения функции приводится к boolean», но я считаю, что единственно правильной является строгая проверка, без приведения типа, если уж вы используете такое объявление переменной.
Я остановился на JsTestDriver по следующим причинам:
* Тесты пишутся в привычном стиле «Класс теста -> методы -> набор assert»
* Инструмент объединяет в себе библиотеку assert и инфраструктуру для запуска тестов в наборе браузеров
* Перехват сообщений от браузеров об ошибках
* Консольный вывод (удобно интегрировался в сборку с помощью Ant и CI на основе Hudson)
* Для написания тестов нужен только js-файл (не нужно писать html, что удобно при работе с ExtJS)
* Легкая расширяемость (хотя видимо этого можно добиться в любой библиотеке, подключив свой js-модуль)
Мануал по пространствам имен на php.net
Да, вот только зачем отвергать решения, которые позволяют решить задачу лучше и проще? Здесь как раз замыкания вполне соответствуют KISS. В любой точке, в которой возможна передача коллбэка, можно, а зачастую и нужно, использовать замыкание, чтобы не плодить методы/наследники-адаптеры.
1. Порционный итератор (получает абстрактный загрузчик данных, передает ему границы порции и получает в ответ данные)
2. Репозиторий сущностей, который не хранит состояние и его метод для получения данных, который требует множество параметров, кроме возможных границ порции.
3. Сервис, который использует доступ к репозиторию с помощью порционного итератора, который тоже не хранит состояние
Используя замыкание мы можем создать легковесный адаптер, не внося изменений в репозиторий, итератор или сервис:
# файл services.yml с описанием зависимостей сервисов
Сервис не должен ничего знать ни о контейнере, ни о геттерах для получения сервисов. У него должен быть интерфейс для внедрения зависимости, а в настройках контейнера вы уже описываете, что передать сервису. Есть еще несколько способов внедрения зависимостей, которые реализованы в Symfony 2, почитать можно здесь и здесь. А в документации можно посмотреть, что еще позволяет сделать контейнер в Symfony 2. А $mailer = $this->container->get('mailer'); это действительно костыль и плохой тон.
Вы можете парировать фразой «я всегда знаю, как результат выполнения функции приводится к boolean», но я считаю, что единственно правильной является строгая проверка, без приведения типа, если уж вы используете такое объявление переменной.
(,).(:)$(,)
— марсианский код какой-то* Тесты пишутся в привычном стиле «Класс теста -> методы -> набор assert»
* Инструмент объединяет в себе библиотеку assert и инфраструктуру для запуска тестов в наборе браузеров
* Перехват сообщений от браузеров об ошибках
* Консольный вывод (удобно интегрировался в сборку с помощью Ant и CI на основе Hudson)
* Для написания тестов нужен только js-файл (не нужно писать html, что удобно при работе с ExtJS)
* Легкая расширяемость (хотя видимо этого можно добиться в любой библиотеке, подключив свой js-модуль)
Пример теста: