가희의자기개발블로그

PHP Closure 본문

프로그래밍 언어/php

PHP Closure

가희gahui 2020. 11. 21. 11:39
반응형

1. 개념 

Closure 클래스는 익명함수를 나타내기 위해 사용된다. 익명함수는 해당 타입의 객체들을 생성해낸다. Closure 클래스는 여러 메소드들을 가지고 있다. 이 메소드들은 익명함수가 생성된 후에, 그것을 보충적으로 컨트롤하는 역할을 한다. 

 

또한 이 클래스는 아래 적혀진 메소드들 외에, __invoke메서드도 가지고 있다. 

 

Closure {
/* Methods */
private __construct ( )
public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure|false
public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure|false
public call ( object $newthis , mixed ...$values ) : mixed
public static fromCallable ( callable $callable ) : Closure
}

 

  • Closure::__construct — 구성자, 생성자 (클래스 선언시 호출되는 메서드)
  • Closure::bind — Duplique une fermeture avec un nouvel objet lié et un nouveau contexte de classe.
  • Closure::bindTo — Duplique la fermeture avec un nouvel objet lié et un nouveau contexte de classe.
  • Closure::call — Lie et appelle la fermeture
  • Closure::fromCallable — Convertis un callable en une fermeture

2. Closure 생성

2.1 Syntaxe

클로저란 이름이 지어지지 않은 특별한 함수이다. 클로저는 아래와 같이 쉽게 선언 할 수 있다.

<?php
function()
{
  echo 'Hello world !';
};

위 코드는 함수로 보이지만 정확히 하자면 하나의 객체이다. 즉, Closure 클래스의 하나의 인스턴스라고 할 수 있다. 

<?php
$maFonction = function()
{
  echo 'Hello world !';
};

var_dump($maFonction); //이 부분에서 클로저 타입이 객체인것을 알수있다.

 

위에서 설명했듯이, Closure 클래스는 매직 메서드인 __invoke()를 가지고 있다. __invoke() 메서드는 객체를 함수로 선언할때 자동으로 호출되는 메서드이다. 즉, 만약 우리가 선언한 객체를 함수로 선언한다면, 우리가 막 선언한 함수가 호출 될것이다.

<?php
$maFonction = function()
{
  echo 'Hello world !';
};

$maFonction(); //  « Hello world ! » 가 출력됨

 

2.2 사용 예시

 

클로저는 보통 콜백함수로 사용된다. 콜백함수는 특정 작업을 수행하기 위해 다른 함수에 의해서 필요로하는 함수이다. 예를 들어 array_map() 함수를 예로 들어보자. 이 함수는 첫번째 파라미터로 적용하고 싶은 함수를 인자로 받고, 두번째 파라미터로는 배열을 입력받는다. 

 

<?php
//아래 클로저는 하나의 아규먼트만을 입력받고 해당 숫자는 array_map에서 다룬다.
$additionneur = function($nbr)
{
  return $nbr + 5;
};

$listeNbr = [1, 2, 3, 4, 5];

$listeNbr = array_map($additionneur, $listeNbr);
//배열이  [6, 7, 8, 9, 10] 로 된것을 알 수 있다.

2.2 외부변수 사용

사실 위에서 작성한 코드는 굉장히 제한적이다. 만약 배열의 각 원소에 4를 더하고 싶다면 또다른 Closure 함수를 만들어야 한다.  즉 외부에서 변수만들어 클로저함수 내부에서 쓸 수 있게 된다면 같은 클로저 함수를 다시 생성해 주지 않아도 된다. 그러기 위해서 use 를 사용한다.

<?php
$quantite = 5;
$additionneur = function($nbr) use($quantite)
{
  return $nbr + $quantite;
};

$listeNbr = [1, 2, 3, 4, 5];

$listeNbr = array_map($additionneur, $listeNbr);

var_dump($listeNbr);
//여기서 배열 [6, 7, 8, 9, 10] 을 얻는다.

 

 여기서 발생할 수 있는 문제는 5로 선언되어있는 quantite 변수는 바뀔 수 없다는 것이다. 왜냐하면 이 변수는 Closure함수에서 이 변수가 사용되었기 때문이다. 아래 코드를 보자

 

<?php
$quantite = 5;
$additionneur = function($nbr) use($quantite)
{
  return $nbr + $quantite;
};

$listeNbr = [1, 2, 3, 4, 5];

$listeNbr = array_map($additionneur, $listeNbr);
var_dump($listeNbr);
// On a : $listeNbr = [6, 7, 8, 9, 10]

$quantite = 4;

$listeNbr = array_map($additionneur, $listeNbr);
var_dump($listeNbr);
// On a : $listeNbr = [10, 11, 12, 13, 14] 대신 [11, 12, 13, 14, 15] 가 출력됨

이런경우 해결할 수 있는 방법은 파라미터를 quantite를 받고  Closure 함수를 return 함수를 하나 만들어서 해결 할 수 있다. 

<?php
function creerAdditionneur($quantite)
{
  return function($nbr) use($quantite)
  {
    return $nbr + $quantite;
  };
}

$listeNbr = [1, 2, 3, 4, 5];

$listeNbr = array_map(creerAdditionneur(5), $listeNbr);
var_dump($listeNbr);
// On a : $listeNbr = [6, 7, 8, 9, 10]

$listeNbr = array_map(creerAdditionneur(4), $listeNbr);
var_dump($listeNbr);
// Cette fois-ci, on a bien : $listeNbr = [10, 11, 12, 13, 14]

위와 같이 함수를 만들어주면 원하는 값을 출력할 수 있다.

 

3. 클로저를 연결하기

3.1 객체와 연결하기

 

클로저는 모든 함수, 객체와 연결할 수 있다. 클로저가 한번 생성되면, 해당 클로저는 우리가 선언한 객체의 일부가 된다는 것이다. 그리고 어트리뷰트와 메소드에 접근할 수도 있다. MaClasse클래스의 _nbr 변수에 5씩 값을 추가하는 additionneur 익명함수를 만들었다. 

 

<?php
$additionneur = function()
{
  $this->_nbr += 5;
};

class MaClasse
{
  private $_nbr = 0;

  public function nbr()
  {
    return $this->_nbr;
  }
}

$obj = new MaClasse;

 

_nbr 변수를 수정하기 위해 $obj 객체에 이 익명함수를 연결해보자. 그러기 위해서는 Closure 클래스가 제공하는 bindTo 메서드를 사용해야 한다. 이 메서드는 2개의 아규먼트를 받는다. 첫번째 아규먼트로는 클로저와 연결하고 싶은 객체이다.(해당 코드에서는 $obj) 두번째 아규먼트는 메서드가 호출될 컨텍스트 (스코프)를 입력받는다. 여기서, 우리는 private 어크리뷰트를 수정하기를 원하기 때문에 메서드를 MaClasse 클래스로 호출해 주어야 한다. 그러기 위해서는 두가지 방법이 있다. 하나는 아래 코드와 같이 « MaClasse »를 문자열로서 전달하는 방법이고 두번째 방법은, MaClasse 객체를 전달해주는 방법이다.(간단히 $obj로)

<?php
$additionneur = function()
{
  $this->_nbr += 5;
};

class MaClasse
{
  private $_nbr = 0;

  public function nbr()
  {
    return $this->_nbr;
  }
}

$obj = new MaClasse;

//아래 코드에서 $obj와 연결된 Closure가 하나 더 생긴다.
//이 새 clousre는 MaClasse의 메서드로서 호출된다.
// 두번째 아규먼트에 'MaClasse'대신 $obj를 보내주어도 된다.
$additionneur = $additionneur->bindTo($obj, 'MaClasse');
$additionneur();

echo $obj->nbr(); // 5 가 출력됨

 

위 코드를 잘 이해하는 것은 매우매우 중요하다..! 아래 다른 예시의 코드를 보자

 

<?php
$additionneur = function()
{
  $this->_nbr += 5;
};

class MaClasse
{
  private $_nbr = 0;

  public function nbr()
  {
    return $this->_nbr;
  }
}

class AutreClasse { }

$obj = new MaClasse;

$additionneur = $additionneur->bindTo($obj, 'AutreClasse');
$additionneur();

//아래 코드는 에러를 발생시킨다. 왜냐면, $_nbr 에 접근하려했지만 해당 변수에 접근할 권한이 없기 때문이다.
echo $obj->nbr();

 

bindTo의 두번째 아규먼트는 무조건 넣어줘야 하는 것은 아니다. 만약 특별히 입력해주지 않는다면,  MaClasse는 특별히 바뀌지 않고 해당 클래스의 private 프로퍼티 및 메서드에 접근할 수 없다. 

 

3.2 객체와 임시로 연결하기

작은 단위의 작업일 경우, 위와 같이 객체와 클로저를 연결하는건 좀 무거울 수 있다. PHP 7버전 부터, 호출하는 순간에 클로저와 객체를 연결하는 방법이 call()메서드로 제공된다. 

 

<?php

class Nombre
{
  private $_nbr;
  
  public function __construct($nbr)
  {
    $this->_nbr = $nbr;
  }
}

$closure = function() {
  var_dump($this->_nbr + 5);
};

$two = new Nombre(2);
$three = new Nombre(3);

$closure->call($two);
$closure->call($three);

위 코드를 보면 첫번째 call()의 결과는 7이고 두번쨰 call() 은 8을 리턴한다. 이게 어떻게 가능할까?

 

  1. 클로저 하나가 생성되었고, 해당 클로저는 _nbr로 넘어온값에 5를 더해 그 결과를 보여주는 역할을 한다. 
  2. 위 코드는 같은 클래스에서 두개의 다른 인스턴스를 가지고 있다. 하나는 _nbr 이 2인 것과 다른 하나는 _nbr이 3인 것이다. 

  3. 클로저와 연결된 첫번째 객체를 선언한다. 해당 클로저 안에 있는 $this->_nbr 명령어는 2를 의미하고 결과적으로 7을 반환한다.  

  4. 그다음 클로저와 연결된 또다른 두번째 객체를 선언한다. 이번에는 $this->_nbr은 3을 의미하고 결과는 8을 리턴한다.

3.3 클래스와 연결하기 

 

위에서 작성한 코드는 클래스와 함수를 연결한다고 할 수 있을까? 그렇지 않다. 위에서 우리가 작성한 코드는 클래스가 어트리뷰트에 접근하는 것이기 때문에 선언한 클래스의 객체가 가지고 있는것이지 클래스가 아니다. 

클래스에 연결하기 위해서는 정적 클로저로 연결 할 수 있다.

 

위의 예시를 다시 가져와, 정적인 컨텍스트에서 클로저가 작동하도록 해보자

 

<?php
$additionneur = function()
{
  self::$_nbr += 5;
};

class MaClasse
{
  private static $_nbr = 0;

  public static function nbr()
  {
    return self::$_nbr;
  }
}

 

이번에 호출한 bindTo()메서드는 위에서 보여준 예시와 조금 다르다. 우리는 object와 클로저를 연결하고 싶은게 아니다. 그러므로 bindTo()의 첫번째 아규먼트로 null을 보내주고, 두번째 아규먼트에 위와 같이 'MaClass'를 보내준다. 왜냐하면  여전히 MaClass 메서드에 클로저를 호출하기 원하기 때문이다. 

 

<?php
$additionneur = function()
{
  self::$_nbr += 5;
};

class MaClasse
{
  private static $_nbr = 0;

  public static function nbr()
  {
    return self::$_nbr;
  }
}

$additionneur = $additionneur->bindTo(null, 'MaClasse');
$additionneur();

echo MaClasse::nbr(); // 5가 출력됨

 

더 자세히 살표보자, 클로저는 늘 객체와 연결 될 수 있다. 만약 선언한 클로저함수가 오직 하나의 클래스와 연결되기를 원한다면 아래 코드와 같이 static 키워드를 이용해서 선언해 줘야한다. 

<?php
// static 익명함수를 선언한다. 
$additionneur = static function()
{
  self::$_nbr += 5;
};

class MaClasse
{
  private static $_nbr = 0;

  public static function nbr()
  {
    return self::$_nbr;
  }
}

$additionneur = $additionneur->bindTo(null, 'MaClasse');
$additionneur();

echo MaClasse::nbr();

3.4 자동 연결

지금까지 한것을 살펴보면, 스크립트의 밖, 즉 글로벌 부분에 클로저를 선언했고, 거기서 우리가 원하는 객체나 클래스와 연결했다. 만약, 메소드의 내부에 클로저를 선언한다면, 그 클로저는 자연히 해당 메소드에 선언되어있는 모든 것들을 이용할 수 있고, 메서드가 static이 아니더라도 바로 해당 객체와 연결된다. 

 

<?php
class MaClasse
{
  private $_nbr = 0;

  public function getAdditionneur()
  {
    return function()
    {
      $this->_nbr += 5;
    };
  }

  public function nbr()
  {
    return $this->_nbr;
  }
}

$obj = new MaClasse;

$additionneur = $obj->getAdditionneur();
$additionneur();

echo $obj->nbr();
//5가 출력된다. 클로저는 MaClasse로부터 $obj에 바로 연결되기 때문
// Affiche bien 5 car notre closure est bien liée à $obj depuis MaClasse

 

이 원리는 static이 선언되어도 똑같이 동작한다.

<?php
class MaClasse
{
  private static $_nbr = 0;

  public static function getAdditionneur()
  {
    return function()
    {
      self::$_nbr += 5;
    };
  }

  public static function nbr()
  {
    return self::$_nbr;
  }
}

$additionneur = MaClasse::getAdditionneur();
$additionneur();

echo MaClasse::nbr(); // Affiche bien 5

 

반응형

'프로그래밍 언어 > php' 카테고리의 다른 글

php 매직 메소드  (0) 2020.11.21
Comments