Search
Write a publication
Pull to refresh
15
0
Imenem @Imenem

User

Send message
Представьте себе, именно так:
Note:

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.

Мануал по пространствам имен на php.net
Сервисы и репозитории в принципе не могут хранить состояние, поэтому в полях класса хранить его нельзя. Оформить в виде класса то, что можно оформить в виде замыкания — громоздко и вряд-ли более понятно. Куча мелких классов-адаптеров — куча мелких файлов в ФС. Передавать аргументами — требует идентичности интерфейсов, а соответственно изменения классов или наследования.
если проектировать не предполагая использование замыканий, то задачу можно решить и без них
Да, вот только зачем отвергать решения, которые позволяют решить задачу лучше и проще? Здесь как раз замыкания вполне соответствуют KISS. В любой точке, в которой возможна передача коллбэка, можно, а зачастую и нужно, использовать замыкание, чтобы не плодить методы/наследники-адаптеры.
Тогда подкрепите свои слова примером того, как можно переписать это без замыканий, не сохраняя состояние в сервисе или репозитории (это идеологически и архитектурно неправильно). Я вот утверждаю, что некоторые куски кода короче, элегантнее и понятнее (выберите любые два :) с использованием замыканий.
Любой, кто знает PHP сможет переписать пример так, чтобы он работал в 5.3:
    public function getActualScheduleItems(DateTime $date = null, DateTimeZone $time_zone = null)
    {
        $repo = $this->getRepo();

        $data_loader = function($offset, $limit) use ($repo, $date, $time_zone)
        {
            return $repo->findActualPeriodicalSchedules($date, $time_zone, $offset, $limit);
        };

        return $this->createPortionIterator($data_loader);
    }
У замыканий есть такое преимущество, как возможность легковесного хранения состояния. Рассмотрим пример в котором имеются:
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();
    }
// файл сервиса
class TestService
{
    /**
     * @var MailerInterface
     */
    protected $mailer;

    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }
}

# файл services.yml с описанием зависимостей сервисов
services:
    test_service:
        class: \TestService
        arguments: [@mailer] 

Сервис не должен ничего знать ни о контейнере, ни о геттерах для получения сервисов. У него должен быть интерфейс для внедрения зависимости, а в настройках контейнера вы уже описываете, что передать сервису. Есть еще несколько способов внедрения зависимостей, которые реализованы в Symfony 2, почитать можно здесь и здесь. А в документации можно посмотреть, что еще позволяет сделать контейнер в Symfony 2. А $mailer = $this->container->get('mailer'); это действительно костыль и плохой тон.
Линус — знатный тролль, он мог сказать все, что угодно.
Вопрос все-таки был «почему все финальное, особенно конструктор». Сеттеры и цепочки методов это конечно хорошо, DI-контейнеры позволяют описать инициализацию один раз и забить, но все-равно, я не вижу смысла лишать этот класс возможности перегрузки методов.
А почему все методы объявлены финальными? А если я хочу, например, изменить папку, в которую пишутся логи или имена файлов логов? Или вместо echo «Starting...\n»; пробросить в конструктор логгер и сделать $this->logger->log(«Starting...\n»);?
У меня вот такое в user.css для хабра:

.post.translation h1.title .post_title
{
    background: #6DA3BD !important;
    padding-left: 0px !important;
}
А, извините, видимо уже спать пора, пока новый элемент не изобрел.
Молекула водорода — 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-модуль)

Пример теста:

(function() {

var SwitcherTest =
{
    object : null,

    setUp : function()
    {
        this.object = JSTime.create('JSTime.widgets.toolbar.Switcher',
        {
            renderTo : Ext.getBody()
        });
    },

    tearDown : function()
    {
        this.object.destroy();
    },

    testToggleButton : function()
    {
        var viewer  = this.object.down('button[sysname=' + this.object.VIEWER + ']'),
            creator = this.object.down('button[sysname=' + this.object.CREATOR + ']');

        this.object.toggleButton(viewer);

        assertTrue(viewer.pressed);
        assertFalse(creator.pressed);

        this.object.toggleButton(creator);

        assertFalse(viewer.pressed);
        assertTrue(creator.pressed);
    },

    testSetActiveItem : function()
    {
        var viewer  = this.object.down('button[sysname=' + this.object.VIEWER + ']'),
            creator = this.object.down('button[sysname=' + this.object.CREATOR + ']');

        this.object.setActiveItem(this.object.VIEWER);

        assertTrue(viewer.pressed);
        assertFalse(creator.pressed);

        this.object.setActiveItem(this.object.CREATOR);

        assertFalse(viewer.pressed);
        assertTrue(creator.pressed);
    },

    testClickButtons : function()
    {
        var viewer  = this.object.down('button[sysname=' + this.object.VIEWER + ']'),
            creator = this.object.down('button[sysname=' + this.object.CREATOR + ']');

        Helpers.click(viewer);

        assertTrue(viewer.pressed);
        assertFalse(creator.pressed);

        Helpers.click(creator);

        assertTrue(creator.pressed);
        assertFalse(viewer.pressed);
    },

    testChangeEvent : function()
    {
        assertEvent(this.object, 'change', function()
        {
            Helpers.click('button[sysname=' + this.object.CREATOR + ']');
        },
        this);

        assertEvent(this.object, 'change', function()
        {
            Helpers.click('button[sysname=' + this.object.VIEWER + ']');
        },
        this);

        assertEvent(this.object, 'change', function()
        {
            this.object.setActiveItem(this.object.CREATOR);
        },
        this);

        assertEvent(this.object, 'change', function()
        {
            this.object.setActiveItem(this.object.VIEWER);
        },
        this);
    }
};

TestCase('SwitcherTest', SwitcherTest);

})();

Information

Rating
Does not participate
Location
Украина
Date of birth
Registered
Activity