Применение замыканий в PHP

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

    Для начала рассмотрим, что же это такое — замыкание и в чем его особенности в PHP.

    $g = 'test';
    
    $c = function($a, $b) use($g){
        echo $a . $b .  $g;
    };
    
    $g = 'test2';
    
    var_dump($c);
    
    /*
    object(Closure)#1 (2)
    {
         ["static"]=> array(1) { ["g"]=> string(4) "test" } 
         ["parameter"]=> array(2) { 
              ["$a"] => string(10) "" 
              ["$b"]=> string(10) ""
          }
    }
    */
    

    Как видим, замыкание как и лямбда-функция представляют собой объект класса Closure, коорый хранит переданные параметры. Для того, чтобы вызывать объект как функцию, в PHP5.3 ввели магический метод __invoke.

    function getClosure()
    {
        $g = 'test';
    
        $c = function($a, $b) use($g){       
            echo $a . $b . $g;        
        };
    
        $g = 'test2';    
        
        return $c;
    }
    
    $closure = getClosure();
    $closure(1, 3); //13test
    
    getClosure()->__invoke(1, 3); //13test
    
    

    Используя конструкцию use мы наследуем переменную из родительской области видимости в локальную область видимости ламбда-функции.
    Ситаксис прост и понятен. Не совсем понятно применение такого функционала в разработке web-приложений. Я просмотрел код нескольких совеременных фреймворков, использующих новые возможности языка и попытался собрать вместе их различные применения.

    Функции обратного вызова


    Самое очевидное применение анонимных функций — использование их в качестве функций обратного вызова (callbacks). В PHP имеется множество стандартных функций, принимающих на вход тип callback или его синоним callable введенный в PHP 5.4. Самые популярные из них array_filter, array_map, array_reduce. Функция array_map служит для итеративной обработки элементов массива. Callback-функция применяется к каждому элементу массива и в качестве результата выдается обработанный массив. У меня сразу возникло желание сравнить производительность обычной обработки массива в цикле с применением встроенной функции. Давайте поэкспериментируем.

    
    $x = range(1, 100000);
    $t = microtime(1);
    
    $x2 = array_map(function($v){
        return $v + 1;
    }, $x);
    //Time: 0.4395
    //Memory: 22179776
    //---------------------------------------
    
    $x2 = array();
    foreach($x as $v){
        $x2[] = $v + 1;
    }
    //Time: 0.0764
    //Memory: 22174788
    //---------------------------------------
    
    function tr($v){
        return $v + 1;
    }
    $x2 = array();
    foreach($x as $v){
        $x2[] = tr($v);
    }
    //Time: 0.4451
    //Memory: 22180604
    

    Как видно, накладные расходы на большое количество вызовов функций дают ощутимый спад в производительности, чего и следовало ожидать. Хотя тест синтетический, задача обработки больших массивов возникает часто, и в данном случае применение функций обработки данных может стать тем местом, которе будет существенно тормозить ваше приложение. Будьте осторожны. Тем не менее в современных приложениях такой подход используется очень часто. Он позволяет делать код более лаконичным, особенно, если обработчик объявляется где-то в другом месте, а не при вызове.

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

    //объявляем функцию квотирования.
    $quoter = function($v) use($pdo){
    	return $pdo->quote($v);//использовать эту функцию не рекомендуется, тем не менее :-)
    }
    $service->register(‘quoter’, $quoter);
    …
    //где-то в другом месте
    //теперь у нас нет доступа к PDO
    $data = array(...);//массив строк
    $data = array_map($this->service->getQuoter(), $data);
    //массив содержит обработанные данные.
    

    Очень удобно применять анонимные функции и для фильтрации

    $x = array_filter($data, function($v){ return $v > 0; });
    //vs
    $x = array();
    foreach($data as $v)
    {
    	if($v > 0){$x[] = $v}
    }
    

    События.


    Замыкания идеально подходят в качестве обработчиков событий. Например

    //где-то при конфигурации приложения.
    $this->events->on(User::EVENT_REGISTER, function($user){
    	//обновить счетчик логинов для пользователя и т.д.
    });
    
    $this->events->on(User::EVENT_REGISTER’, function($user){
    	//выслать email для подтверждения.
    });
    
    //в обработчике формы регистрации
    $this->events->trigger(User::EVENT_REGISTER, $user);
    

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

    Валидация


    Замыкания по сути сохраняют некоторую логику в переменной, которая может быть выполнена или не выполнена в по ходу работы скрипта. Это то, что нужно для реализации валидаторов:

    $notEmpty = function($v){ return strlen($v) > 0 ? true : “Значение не может быть пустым”; };
    $greaterZero = function($v){ return $v > 0 ? true : “Значение должно быть больше нуля”; };
    
    function getRangeValidator($min, $max){
    	return function($v) use ($min, $max){
    		return ($v >= $min && $v <= $max) 
                             ? true 
                             : “Значение не попадает в промежуток”;
    	};
    }
    

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

    class UserForm extends BaseForm{
    
        public function __constructor()
        {
            $this->addValidator(‘email’, Validators::$notEmpty);
            $this->addValidator(‘age’, Validators::getRangeValidator(18, 55));
            $this->addValidator(‘custom’, function($v){
    		//some logic
            });
        }
    
        /**
        * Находится в базовом классе.
        */
        public function isValid()
        {
            …
            $validationResult = $validator($value);
            if($validationResult !== true){
                $this->addValidationError($field, $validationResult);
            }
            …
        }
    }
    

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

    Выражения


    В Symfony встречается очень интересное применение замыканий. Класс ExprBuilder опеделяет сущность, которая позволяет строить выражения вида

    ...
    ->beforeNormalization()
        ->ifTrue(function($v) { return is_array($v) && is_int(key($v)); })
        ->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); })
     ->end()
    ...
    

    В Symfony как я понял это внутренний класс, который используется для создания обработки вложенных конфигурационных массивов (поправьте меня, если не прав). Интересна идея реализации выражений в виде цепочек. В принципе вполне можно реализовать класс, который бы описывал выражения в таком виде:

    $expr = new Expression();
    
    $expr
    ->if(function(){ return $this->v == 4;})
    ->then(function(){$this->v = 42;})
    ->else(function(){})
    	->elseif(function(){})
    ->end()
    ->while(function(){$this->v >=42})
    	->do(function(){
    		$this->v --;
    })
    ->end()
    ->apply(function(){/*Some code*/});
    
    $expr->v = 4;
    $expr->exec();
    echo $expr->v;
    

    Применение, конечно, экспериментально. По сути — это запись некоторого алгоритма. Реализация такого функционала достаточно сложна — выражение в идеальном случае должно хранить дерево операций. Инетересна концепция, может быть где-то такая конструкция будет полезна.

    Роутинг


    Во многих мини-фреймворках роутинг сейчас работает на анонимных функциях.

    App::router(‘GET /users’, function() use($app){
        $app->response->write(‘Hello, World!’);
    });
    

    Достаточно удобно и лаконично.

    Кеширование


    На хабре это уже обсуждалось, тем не менее.

    $someHtml = $this->cashe->get(‘users.list’, function() use($app){
    	$users = $app->db->table(‘users)->all();
    	return $app->template->render(‘users.list’, $isers);
    }, 1000);
    

    Здесь метод get проверяет валидность кеша по ключу ‘users.list’ и если он не валиден, то обращается к функции за данными. Третий параметр определяет длительность хранения данных.

    Инициализация по требованию


    Допустим, у нас есть сервис Mailer, который мы вызываем в некоторых методах. Перед использованием он должен быть сконфигурирован. Чтобы не инициализировать его каждый раз, будем использовать ленивое создание объекта.

    //Где-то в конфигурационном файле.
    $service->register(‘Mailer’, function(){
    	return new Mailer(‘user’, ‘password’, ‘url’);
    });
    
    //Где-то в контроллере
    $this->service(‘Mailer’)->mail(...);
    

    Инициализация объекта произойдет только перед самым первым использованием.

    Изменение поведения объектов


    Иногда бывает полезно переопределить поведение объектов в процессе выполнения скрипта — добавить метод, переопределить старый, и т.д. Замыкание поможет нам и здесь. В PHP5.3 для этого нужно было использовать различные обходные пути.

    class Base{
        
        public function publicMethod(){echo 'public';}
        private function privateMethod(){echo 'private';}    
        //будем перехватывать обращение к замыканию и вызывать его.
        public function __call($name, $arguments) {
            if($this->$name instanceof Closure){
                return call_user_func_array($this->$name, array_merge(array($this), $arguments));
            }
        }
    }
    
    $b = new Base; 
    
    //создаем новый метод
    $b->method = function($self){
        echo 'I am a new dynamic method';
       $self->publicMethod(); //есть доступ только к публичным свойствам и методам
    };
    
    $b->method->__invoke($b); //вызов через магический метод
    
    $b->method(); //вызов через перехват обращения к методу
    
    //call_user_func($b->{'method'}, $b); //так не работает
    


    В принципе можно и переопределять старый метод, однако только в случае если он был определен подобным путем. Не совсем удобно. Поэтому в PHP 5.4 появилось возможность связать замыкание с объектом.

    $closure = function(){
    	return $this->privateMethod();
    }
    
    $closure->bindTo($b,  $b); //второй параметр определяет область видимости
    $closure();
    

    Конечно, модификации объекта не получилось, тем не менее замыкание получает доступ к приватным функциям и свойствам.

    Передача как параметры по умолчанию в методы доступа к данным


    Пример получения значения из массива GET. В случае его отсутствия значение будет получено путем вызова функции.
    $name = Input::get('name', function() {return 'Fred';});
    


    Функции высшего порядка


    Здесь уже был пример создания валидатора. Приведу пример из фреймворка lithium

    /**
     * Writes the message to the configured cache adapter.
     *
     * @param string $type
     * @param string $message
     * @return closure Function returning boolean `true` on successful write, `false` otherwise.
     */
    public function write($type, $message) {
    	$config = $this->_config + $this->_classes;
    
    	return function($self, $params) use ($config) {
    		$params += array('timestamp' => strtotime('now'));
    		$key = $config['key'];
    		$key = is_callable($key) ? $key($params) : String::insert($key, $params);
    
    		$cache = $config['cache'];
    		return $cache::write($config['config'], $key, $params['message'], $config['expiry']);
    	};
    }
    

    Метод возвращает замыкание, которое может быть использовано потом для записи сообщения в кеш.

    Передача в шаблоны


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

    //В контроллере
    $layout->setLink = function($setId) use ($userLogin)
    {
        return '/users/' . $userLogin . '/set/' . $setId;
    };
    
    //В шаблоне
    <a href=<?=$this->setLink->__invoke($id);?>>Set name</a>
    

    В данном случае в шаблоне генерировалось несколько ссылок на сущности пользователя и в адресах этих ссылок фигурировал его логин.

    Рекурсивное определение замыкания


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

    $factorial = function( $n ) use ( &$factorial ) {
        if( $n == 1 ) return 1;
        return $factorial( $n - 1 ) * $n;
    };
    
    print $factorial( 5 );
    


    Многие из примеров выглядят натянуто. Сколько лет жили без них — и ничего. Тем не менее иногда применение замыкания достаточно естественно и для PHP. Умелое использование этой возможности позволит сделать код более читаемым и увеличить эффективность работы программиста. Просто нужно немного подстроить свое мышление под новую парадигму и все станет на свои места. А вообще рекомендую сравнить, как используются такие вещи в других языках типа Python. Надеюсь, что кто-нибудь нашел для себя здесь что-то новое. И конечно, если кто-то знает еще какие-нибудь интересные применения замыканий, то очень жду ваши комментарии. Спасибо!
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 51

      +12
      На PHP 5.3 обратил внимание на такую особенность: переменные, упомянутые в use(), если являлись «ссылками», то перестают ими быть.

      Например

      $contracts  = ...;
      $services = ...;
      
      foreach ($contracts as &$contract) {
      
          $contract['xxx'] = 'yyy'; // тут меняется $contracts
          $contractServices = array_filter($services, function($v) use ($contract){
               return $v['id'] == $contract['contractId'];
          });
      
          $contract['mmm'] = 'nnn'; // а здесь $contract уже живёт своей жизнью, $contracts не меняется
      
      }
      


      Проблему решает амперсенд — use (&$contract), но, имхо, такое поведение оказалось неочевидным.
        +3
        Да. Поэтому и при рекурсивном определении замыкания в use передается ссылка с амперсандом, несмотря на то, что параметр и так объект и должен передаваться по ссылке. Скорее всего это потому что, use — это не передача параметров в функцию, а разрешение на доступ к переменным родительской области видимости после определения функции. Поэтому по-умолчанию происходит копирование значений, если явно не указана передача по ссылке.
          +1
          Спасибо. Путает скорее название конструкции — «use», подсознательно предполагаешь, что оно будет использовать имеющиеся инстансы переменных вместо их клонирования :) Мысленно ставлю вам плюс.
          0
          Что-то я не понял смысла в амперсанде вот тут: foreach ($contracts as &$contract)
          0
          Если я правильно понял, то это баг поправили.
            0
            Вы просто не поняли суть замыканий. Перечисленное в use захватыется с теми значениями, которые оно было на момент вычисления выражения с замыканием.
            Например для простоты, пусть у вас обычный нумерованный массив каких-то объектов (с индексами 0, 1, 2 и т.п.). На каждый объект вам нужно повесить одинаковый callback или event, но внутри этого callback-а вам нужно знать индекс обрабатываемого объекта в массиве, а взять снаружи скажем неоткуда (ну скажем этот callback вызывается из какой-то библиотеки, которая рассчитана на одиночные объекты и знать не знает про ваши массивы). Вот здесь вы используете как раз замыкания
            for($i=0;$i<count($obj_array);++$i) 
                $obj_array[$i].onevent = function () use($i) { /*echo $i...*/}
            

            Если бы не было замыкания, вы бы эту $i никак не вытащили.
              0
              Передать ее параметром в само замыкание не судьба?
                0
                Нет, если этот onevent вызывается какой-то сторонней библиотекой.
            0
            По-моему замыкания стоит использовать только в случае, когда очень(!) необходимо сохранить состояние и в обработчиках (не более 5 на событие). Остальное все от лукавого
              +6
              Вот вам еще в копилку, из реального проекта, обертка для 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();
                  }
                +1
                Как видим, замыкание как и лямбда-функция представляют собой объект класса Closure

                Стоит уточнить, наверное, что лямбда-функция это частный случай замыкания.

                А использование замыканий в PHP отчасти непопулярно, по-моему, из-за специфики PHP, работающего в CGI-режиме (логически). Грубо говоря нет никакой разницы писать function ($request_param, $config_param) или function ($request_param) use ($config_param), если $config_param вычисляется при каждом запросе так же как $request_param.
                  0
                  Примеры кода на PHP это классно, но по-моему большинство идей использования можно почерпнуть из en.wikipedia.org/wiki/Closure_(computer_science)
                    +2
                    Я вот не вижу преимуществ использования замыканий. Почти все можно реализовать без их использования, но используя их, код становится сложнее и менее понятным, его сложнее поддерживать и развивать. В определенный момент поддержка такого кода может отнять времени больше чем было сэкономлено при написании кода с использованием замыканий.
                      0
                      У замыканий есть такое преимущество, как возможность легковесного хранения состояния. Рассмотрим пример в котором имеются:
                      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);
                          }
                      
                        0
                        Понятное дело что удобнее, я этого не отрицал. Согласитесь, можно этот код написать и не используя замыкания. Я писал о том, что замыкания усложняют отладку и поддержку кода. А если неаккуратно ними пользоваться, то может еще и память течь.
                          0
                          Тогда подкрепите свои слова примером того, как можно переписать это без замыканий, не сохраняя состояние в сервисе или репозитории (это идеологически и архитектурно неправильно). Я вот утверждаю, что некоторые куски кода короче, элегантнее и понятнее (выберите любые два :) с использованием замыканий.
                            0
                            Просто взять и переписать, сохранив логику примера выше, красиво не выйдет. Весь дизайн кода будет отличаться, вот в примере предполагается сохранение состояние. Изменив дизайн, его можно сохранять в полях класса. Можно передавать постоянно аргументами, можно просто оформить в виде класса. Я не говорю что все сказанное применимо к этому примеру, замечу еще раз, если проектировать не предполагая использование замыканий, то задачу можно решить и без них. Просто иной подход будет.
                              0
                              Сервисы и репозитории в принципе не могут хранить состояние, поэтому в полях класса хранить его нельзя. Оформить в виде класса то, что можно оформить в виде замыкания — громоздко и вряд-ли более понятно. Куча мелких классов-адаптеров — куча мелких файлов в ФС. Передавать аргументами — требует идентичности интерфейсов, а соответственно изменения классов или наследования.
                              если проектировать не предполагая использование замыканий, то задачу можно решить и без них
                              Да, вот только зачем отвергать решения, которые позволяют решить задачу лучше и проще? Здесь как раз замыкания вполне соответствуют KISS. В любой точке, в которой возможна передача коллбэка, можно, а зачастую и нужно, использовать замыкание, чтобы не плодить методы/наследники-адаптеры.
                          +1
                          Стоит, наверное, уточнить, что этот пример будет работать только с версии 5.4 из-за указателя $this внутри замыкания.
                            0
                            Любой, кто знает 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);
                                }
                              0
                              Несомненно, я лишь хотел съэкономить немного времени тем, кто только разбирается с замыканиями и не знает про ньюанс с $this, тем более, что в статье про это ничего не сказано.
                          +1
                          Очень удобно передавать callback во всякие array_filter. Бывает удобно передать функцию куда-то внутрь.
                          +2
                          Замыкания прекрасны, пусть и не настолько удобны и лаконичны, как в ECMAScript. Главное использовать их с умом и только тем, где это действительно удобно и нужно, а не потому что «это круто, модно, современно». А то есть риск получить неизлечимую злокачественную опухоль.
                            0
                            $expr
                            ->if(function(){ return $this->v == 4;})
                            ->then(function(){$this->v = 42;})
                            ->else(function(){})
                                ->elseif(function(){})
                            ->end()
                            ->while(function(){$this->v >=42})
                                ->do(function(){
                                    $this->v --;
                            })
                            ->end()
                            

                            ИМХО попахивает Монадами.
                              –1
                              Крайний с конца пример как бы намекает нам на возможность развития функционального программирования в PHP.
                              +4
                              «замыкание как и лямбда-функция представляют собой объект класса Closure»
                              Автор, вы уж почитайте до конца что такое лямбда функция и что такое замыкание. Лямбда функция — это то что вы называете замыканием, и именно она есть обтект класса Closure, а замыкание это действие при котором этот обьект помнит что находилось вокруг него при создании (именно кусочек который прячется за оператором use).
                                0
                                Спасибо за уточнение. Вы правы.
                                0
                                Валидация — 1-й пример:

                                return frunction($v) use ($min, $max){


                                В слове frunction — ошибка
                                  0
                                  Спасибо, исправил
                                    0
                                    А минус-то за что? Странно…
                                    +1
                                    Замыкания — элегантный способ делать Dependancy Injection. См. например, прекрасный мини-фреймворк Silex.
                                    Предположим, у вас есть некий интерфейс (скажем той же callback функции), который вы менять не хотите или не можете.
                                    Но вам в каком-то месте потребовалось сделать функцию с этим интерфейсом, которой нужно зачем-то лезть в базу данных.
                                    Откуда эта функция возьмет объект соединения с БД?
                                    До PHP 5.3. практически единственным способом было объявить глобальную переменную global $db, или ну или global $app; $db = $app->db;
                                    Но это засоряет глобальную область видимости и не очень работает, если у вас несколько соединений с дб.
                                    С замыканиями вы просто объявляете функцию с нужным интерфейсом и передаете ей линк на $db через use ($db) в момент объявления.
                                      –2
                                      Простите, я вот понять не могу этих нововведений. PHP != Javascript. Он не событийный язык. Я не могу понять, зачем из танка самолет делать?
                                        +1
                                        Лямбда функции не только в JS используются и не только для событий.
                                          0
                                          переменная = функция, use и т.д. есть и в С++11x
                                            0
                                            Замыкания редко вот прям чтоб необходимы, но во многих случаях просто удобны своим простым синтаксисом.
                                            Ну как пример — таблица футбольного чемпионата, нужно сделать сортировку, кто какое место занял по заданной турнирной таблице (там трюки при равенствен очков), при этом очень хочется использовать стандартный алгоритм uasort (ну не писать же свой пузырёк в тысячный раз, правильно).
                                            Попробуйте сделать это с замыканиями и без — поймете разницу.
                                              +1
                                              Вы очень сильно ошибаетесь насчёт «PHP это не событийный язык». Костыли по поводу внедрения событий в PHP встречаются ещё со времён первой версии Pear'а, а сейчас это имеет массовый характер. Если во фреймворке нет поддержки событий (хуков), то он плох. Сейчас наступают времена асинхронности, а там без событий ну никак нельзя, поэтому готовьтесь. Читайте мануал по phpDaemon и привыкайте к событийности ))
                                                0
                                                Я, коллега, все прекрасно понимаю, и понимаю куда все движется. И готовлюсь — изучаю Node.js
                                                И лично я считаю что в PHP это все и останется костылями, ибо чтобы это все _действительно_ работало, нужна другая идеология построения, как протокола, так и веб сервера, так и протокола http. И для человека, который хоть как то работал с javascript, все эти нововведения в PHP, ну ни как новыми не кажутся :-)
                                                  0
                                                  Ну а как же phpDaemon? Активно развивается. И хотя мне кажется, что nodejs местами попроще, в phpd есть свой скрытый смысл, преимущества над nodejs. Там кстати тоже уже есть и WS-клиенты и всякие XMPP-протоколы поддерживаются.
                                                    0
                                                    По мне, это костыль. Ну для того, чтобы это все действительно работало, нужно другой, абсолютно другой подход ко всей клиент-серверной структуре. Node.js — это и веб-сервер и язык программирование, все вместе. Именно это дает такую чудовищную мощность и гибкость. Linux + Apach + MySQL + PHP !== node.js. Я даже не знаю как выразиться точней, ибо я не могу языком PHP выразить то, что выражаю nodejs, и появление таких конструкций, как [] и {} в PHP 5.4, а также, замыканий, анонимных функций, глазами человека, который практикует javascript + nodejs выглядит… ну просто обескураживающей. Ну это просто мысли в слух, ни ради холивара. Мне очень нравится PHP, очень, и он действительно проще, чем javascript, и в первую очередь, на мой взгляд, благодаря синхронному выполнению и отсутствия событий, анонимных функций, замыканий и т.д.
                                                      +1
                                                      Вы смотрели phpd? Там нет никаких Apache, он сам себе сервер. По сути это то же самое, что и nodejs, только на php. Без linux и mysql вы не обойдётесь, они сами по себе, отдельно.
                                                      К сожалению, того PHP, которого мы знали 5 лет назад, скоро уже не будет. Всё будет асинхронно и персистентно)
                                              0
                                              Наконецто можно нормально использовать array_map и прочее. Писать имена функций определенных рядом строкой, ну совсем неприятно было.
                                                +1
                                                :%s/наконецто/наконец-то/g
                                                  0
                                                  //Time: 0.4395
                                                  //Time: 0.0764
                                                  А мне это уже не кажется хорошей идеей
                                                    0
                                                    Не знаю что за цифры вы привели. У меня таких отличий в производительности не наблюдается.

                                                    test1.php
                                                    <?php
                                                    $array = range(0, 100000);
                                                    $result = array_map(function($element) { return $element+1; },$array);
                                                    


                                                    test2.php
                                                    <?php
                                                    $array = range(0, 100000);
                                                    function test($element) { return $element+1;}
                                                    array_map('test',$array);
                                                    

                                                      0
                                                      Разница не между способом вызова анонимной функции, а между обработкой каждого элемента данных с помощью анонимной функции и обычной итерацией массива.
                                                        0
                                                        Ясно, исходил из темы топика.
                                                  +1
                                                  >>$name = Input::get('name', function() {return 'Fred';});
                                                  Вот зачем изобретать велосипед, если и короче и понятнее можно написать
                                                  $name=isset($_GET['name'])?$_GET['name'] :'Fred');
                                                    0
                                                    Пример может быть более сложным, например, значение по умолчанию нужно брать из базы.

                                                    $name = Input::get('name', function() use ($db){$config = $db->loadConfig(); return $config['default_name'];});
                                                    


                                                    В Вашем же случае либо придётся написать дополнительное условие

                                                    $name=isset($_GET['name'])?$_GET['name'] :null); 
                                                    if (is_null($name))
                                                      {
                                                      $config = $db->loadConfig(); 
                                                      $name = $config['default_name'];
                                                      }
                                                    


                                                    , либо при каждом запросе обращаться к базе.

                                                    $config = $db->loadConfig(); 
                                                    $default_name = $config['default_name'];
                                                    $name=isset($_GET['name'])?$_GET['name'] :$default_name); 
                                                    

                                                  Only users with full accounts can post comments. Log in, please.