마젠토 _construct() vs __construct()

PHP 생성자 __construct()

PHP에서 클래스의 생성자(또는 constructor) “__construct()”는 클래스의 객체 생성시 내부에서 자동으로 호출되며 기본적인 속성들이 초기화 되는 과정을 포함하고 있는 멤버함수 입니다. PHP5에서 생성자는 반드시 필요한 함수는 아니지만 객체가 생성될때 변수가 필요하거나 멤버변수의 초기화가 필요하다면 생성자가 있어야 합니다. 아래의 코드는 PHP 생성자의 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Product
{
    private $_sku;
    public __construct($sku)
    {
        $this->_sku = $sku;
    }

    public function getSku()
    {
        return $this->_sku;
    }
}

$product = new Product('sample_sku');
echo $product->getSku(); // "sample_sku"를 출력
?>

마젠토 생성자 _construct()

마젠토의 클래스들을 잘 살펴보면 이 생성자 “__construct()” 함수를 찾아보기 힘듭니다. 대신 얼핏보면 생성자 함수처럼 보이는 “_construct()”를 발견할수 있습니다.
두개의 다른점은 일단 보이는대로 언더스코어 “_”가 PHP의 생성자에는 두개가 사용되지만 마젠토의 클래스에서 생성자 역할을 하는 함수에는 언더스코어가 하나밖에 사용되지 않습니다. 결론적으로 마젠토의 생성자 “_construct()”는 PHP 자체에서는 어떤 의미도 가지지 않고, 단지 마젠토에서만 생성자의 역할을 하는 함수일 뿐입니다.
마젠토의 클래스들은 99%(Observer는 제외) “/lib/Varien/Object.php의 “Varien_Object” 클래스에서 상속되었기 때문에 거의 모든 클래스에 “_construct()” 멤버함수가 존재합니다.
“Varien”은 마젠토가 이베이에 인수되기전의 회사이름이었구요, “Varien_Object”는 마젠토 프로그래밍에서 자주 사용되는 get/set/uns 및 마젠토 객체에서 기본적으로 사용되는 속성이나 기능을 가지고 있는 클래스입니다.
아래는 /lib/Varien/Object.php 파일의 생성자 부분만 캡쳐해온 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
class Varien_Object implements ArrayAccess
{
    public function __construct()
    {
        $this->_initOldFieldsMap();
        if ($this->_oldFieldsMap) {
            $this->_prepareSyncFieldsMap();
        }

        $args = func_get_args();
        if (empty($args[0])) {
            $args[0] = array();
        }
        $this->_data = $args[0];
        $this->_addFullNames();

        $this->_construct();
    }

    /**
     * Internal constructor not depended on params. Can be used for object initialization
     */

    protected function _construct()
    {
    }
}
?>

클래스의 생성자를 들여다 보면 생성자 함수의 하단에 “$this->_construct();” 를 발견할수 있습니다.
반면에 “_construct()” 함수는 “생성자가 변수에 의존하지 않으며 객체 생성에 사용된다”는 코멘트와 함께 비어있습니다.
결론적으로 이 “_construct()” 함수는

  • 마젠토 개발 용도로 만들어진 특별한 생성자 함수이며
  • 개발자가 모듈개발을 하거나 기존의 기능을 override 하는 경우 자동으로 Varien_Object 모델을 상속하게 만들어주고
  • 새로 만드는 클래스의 생성자에 “parent::__construct();”를 넣지 않아도 되는 편의성을 제공합니다.

아래의 예제 코드를 보면 조금더 쉽게 이해가 되리라 생각합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class ParentClass
{
    public function __construct()
    {
        $this->helloWorld();
    }

    public function helloWorld()
    {
        echo "hello world, ";
    }
}

class ChildClass extends ParentClass
{
    public function __construct()
    {
        parent::__construct();
        echo "this is child class";
    }
}

$parent = new ParentClass(); // "hello world, " 를 출력
$child = new ChildClass(); // "hello world, this is child class" 출력
?>

PHP는 자동으로 부모클래스의 생성자를 실행하지 않기 때문에 위의 ChildClass 처럼 ParentClass의 생성자를 override 했을때 “parent::__construct();”를 추가해야만 부모클래스의 생성자를 실행하게 되어 ParentClass::helloworld() 멤버함수를 실행하게 됩니다.
아래는 마젠토 방식의 생성자 예제 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class ParentClass
{
    public function __construct()
    {
        $this->helloWorld();
        $this->_construct();
    }

    public function helloWorld()
    {
        echo "hello world, ";
    }

    public function _construct()
    {
    }
}

class ChildClass extends ParentClass
{
    public function _construct()
    {
        echo "this is child class";
    }
}

$parent = new ParentClass(); // "hello world, " 출력
$child = new ChildClass(); // "hello world, this is child class" 출력
?>

위의 예제에서 ChildClass에는 PHP 생성자 “__construct()”가 없습니다. 그러므로 자동으로 부모클래스인 ParentClass의 생성자를 실행시키게 되고 ParentClass의 생성자가 필요한 로직을 모두 실행하면서 ChildClass의 _construct()을 실행하게 됩니다.

위의 코드처럼 마젠토에서는 PHP 생성자 “__construct()”를 사용하지 않고 마젠토만의 “_construct()” 함수를 사용함으로써 자동으로 부모클래스의 생성자를 실행시킴과 동시에 “parent::__construct();”를 자식클래스에 포함시키지 않아도 되게끔 만들었습니다. 물론 자식클래스에 PHP 생성자를 만들고 거기에 “parent::__construct()”를 포함시킬 수 있지만 마젠토 개발자라면 “_construct()” 멤버함수를 사용하는게 올바른 마젠토 프로그래밍 방법입니다.

마젠토2 – 의존성 주입(Dependency Injection) 디자인 패턴

PHP 의존성 주입(Dependency Injection, DI) 디자인 패턴의 기본 컨셉

예를들어 A객체 내에서 B객체가 생성된다면 A객체는 B객체에 의존성을 갖게 됩니다. B객체 생성자의 소스코드가 수정된다면 A객체의 소스코드 또한 바뀌어야 하기 때문에 A객체는 B객체에 의존성을 갖게 되는 것입니다. 의존성 주입(Dependency Injection, DI)은 이러한 객체간의 의존성을 제거하는 디자인 패턴입니다. 쉽게 말해서 A객체 내에 B객체가 필요하다면 A객체 내에서 B객체를 생성시키지 않고 외부에서 B객체를 생성시킨후 A객체 안에 주입시키는 것이 의존성 주입(DI)입니다. 의존성 주입(DI)을 사용하게 되면 모듈간의 의존성을 없애기 때문에 좀 더 수월한 유닛테스팅을(unit testing) 할수 있게 해주고 코드의 재사용(code reuse) 및 코드관리에 있어서도 많은 편의성을 제공해 줍니다.

그렇다면 이 의존성 주입이 무엇인지 가장 기본적인 예제를 통해 살펴보도록 하겠습니다. 아래의 코드는 의존성 주입 패턴을 사용하지 않은 코드의 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
class Address
{
    private $_country;

    public function __construct($country)
    {
        $this->_country = $country;
    }

    public function getCountry()
    {
        return $this->_country;
    }
}

class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, $country)
    {
        $this->_name = $name;
        $this->_address = new Address($country);
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

위 예제의 경우 Customer 객체 안에서 새로운 Address 객체를 생성하기 때문에 Customer 클래스는 Address 클래스에 의존하게 됩니다. 특별히 문제될것은 없어보이지만 만약 Address::__construct() 멤버함수가 변경이 된다면 Customer 클래스에서도 코드 변경을 해야할 경우가 생기기도 합니다.
예를들어 Address 클래스가 아래와 같이 변경된다면 어떨까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Address
{
    private $_city;
    private $_country;

    public function __construct($city, $country)
    {
        $this->_city = $city;
        $this->_country = $country;
    }

    public function getCity()
    {
        return $this->_city();
    }

    public function getCountry()
    {
        return $this->_country();
    }
}
?>

위와같이 Address 클래스의 생성자 변수가 $_city, $_country 두가지로 늘어났기 때문에 Customer 클래스처럼 Address 객체를 생성시키는 모든 클래스를 수정해야 합니다. 위에서 제시한 예제의 경우 Customer 클래스를 아래와 같이 변경시켜야 제대로 작동 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, $city, $country)
    {
        $this->_name = $name;
        $this->_address = new Address($city, $country);
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

하지만 이런식으로 Address 클래스의 생성자가 변경될때마다 매번 Customer 클래스를 수정해야 하는것은 불편하고 비효율적 이므로 아예 Address 객체를 주입하는걸 어떨까요? 아래의 예제는 의존성 주입 패턴을 사용한 Customer 클래스 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, Address $address)
    {
        $this->_name = $name;
        $this->_address = $address;
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

위와 같이 수정을 해서 Address 객체를 Customer에 생성자의 변수로 직접 주입하게 되면 Address 클래스에서 어떤 수정이 이루어지던 Customer 클래스를 수정할 필요가 없어집니다. 이런 의존성 주입(Dependency Injection, DI) 디자인 패턴은 데모용 프로젝트나 규모가 작은 프로젝트에는 굳이 적용할 필요가 없지만, 프로젝트가 지속적으로 업데이트되고 규모가 커질수록 의존성 주입 패턴은 모듈간의 의존성을 제거시키는데 큰 역할을 하게 되고 소스 코드를 유지 보수 하는데 큰 도움이 됩니다. 같은 이유로 “마젠토2″에서도 “마젠토1″에서는 사용하지 않았던 의존성 주입 패턴을 사용하고 있습니다.

마젠토2″에서 사용된 의존성 주입 – Object Manager 와 di.xml의 사용

마젠토 2 – 의존성 주입 (Dependency Injection)
아래 코드는 “마젠토 2″의 Magento\Catalog\Model\Product::__construct() 멤버함수 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Product extends \Magento\Catalog\Model\AbstractModel implements
    IdentityInterface,
    SaleableInterface,
    ProductInterface
{
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Catalog\Api\ProductAttributeRepositoryInterface $metadataService,
        AttributeDataBuilder $customAttributeBuilder,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        Product\Url $url,
        Product\Link $productLink,
        \Magento\Catalog\Model\Product\Configuration\Item\OptionFactory $itemOptionFactory,
        \Magento\CatalogInventory\Api\Data\StockItemDataBuilder $stockItemBuilder,
        \Magento\Catalog\Model\Product\Option $catalogProductOption,
        \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
        \Magento\Catalog\Model\Product\Attribute\Source\Status $catalogProductStatus,
        \Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig,
        Product\Type $catalogProductType,
        \Magento\Catalog\Helper\Image $catalogImage,
        \Magento\Framework\Module\Manager $moduleManager,
        \Magento\Catalog\Helper\Product $catalogProduct,
        Resource\Product $resource,
        Resource\Product\Collection $resourceCollection,
        \Magento\Framework\Data\CollectionFactory $collectionFactory,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Indexer\Model\IndexerRegistry $indexerRegistry,
        \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
        \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
        \Magento\Catalog\Model\Indexer\Product\Eav\Processor $productEavIndexerProcessor,
        CategoryRepositoryInterface $categoryRepository,
        array $data = []
    ) {
        $this->_itemOptionFactory = $itemOptionFactory;
        $this->_stockItemBuilder = $stockItemBuilder;
        $this->_optionInstance = $catalogProductOption;
        $this->_catalogProductVisibility = $catalogProductVisibility;
        $this->_catalogProductStatus = $catalogProductStatus;
        $this->_catalogProductMediaConfig = $catalogProductMediaConfig;
        $this->_catalogProductType = $catalogProductType;
        $this->_catalogImage = $catalogImage;
        $this->moduleManager = $moduleManager;
        $this->_catalogProduct = $catalogProduct;
        $this->_collectionFactory = $collectionFactory;
        $this->_urlModel = $url;
        $this->_linkInstance = $productLink;
        $this->_filesystem = $filesystem;
        $this->indexerRegistry = $indexerRegistry;
        $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
        $this->_productPriceIndexerProcessor = $productPriceIndexerProcessor;
        $this->_productEavIndexerProcessor = $productEavIndexerProcessor;
        $this->categoryRepository = $categoryRepository;
        parent::__construct(
            $context,
            $registry,
            $metadataService,
            $customAttributeBuilder,
            $storeManager,
            $resource,
            $resourceCollection,
            $data
        );
    }
}

위의 “마젠토2” 코드는 확실히 복잡해 보이며 아마도 “new” 를 사용하거나 기존의 마젠토에서 사용하던 방법으로 객체를 생성하는 일은 거의 불가능해 보입니다.
하지만 “마젠토2″에서는 모든 객체를 관리하는 “Object Manager”(Magento\App\ObjectManager)가 새롭게 생겼구요 모든 객체의 생성은 이 “Object Manger”를 통해서 생성됩니다.
기존의 마젠토에서 아래같은 코드를 사용해서 product 객체를 생성했다면,

1
$product = Mage::getModel('catalog/product');

마젠토2 에서는 아래의 코드처럼 ObjectManager를 사용해서 product 객체를 생성하게 됩니다.

1
$product = $this->_objectManager->get('Magento\Catalog\Model\Product');

그리고 “Object Manager”와 함께 “di.xml” 파일도 새롭게 생겼는데 파일 이름 자체가 Dependency Injection(DI)에서 따온걸로 보이는 이 파일은 Object Manager에 필요한 모듈이나 객체의 의존성 주입 관련 설정을 담고있구요, 애플리케이션이나 모듈의 레벨에 따라 다른 위치에 저장됩니다.
– 애플리케이션의 글로벌 설정일 경우: app/etc/di.xml
– 모듈의 글로벌 설정일 경우: {module}/etc/di.xml
– 모듈의 특정 지역 설정일 경우: {module}/etc/{areaCode}/di.xml

제가 처음으로 마젠토를 접했을때 다른 e-commerce 플랫폼보다 좀 복잡하다는 느낌을 받았는데 마젠토2 또한 새로운 컨셉이나 기능들이 더 생겨나서 적응하는데 시간이 좀 걸릴듯 합니다. 계획대로라면 마젠토2는 내년 4분기에나 접할수 있을것으로 보이지만 기본적인 컨셉은 미리미리 알아두는게 좋을듯 합니다. 게다가 의존성 주입(Dependency Injection)같이 소프트웨어 개발에서 자주 쓰이는 디자인 패턴은 굳이 마젠토 때문이 아니라도 알아두면 좋구요, 마젠토 개발자라면 마젠토2의 Object Manager나 dl.xml같이 새롭지만 핵심적인 개념/기능들을 숙지하고 있어야 할 것 같습니다.