nattapong99 / book

0 stars 0 forks source link

Part 5: Symfony Service Container: Using XML or YAML to describe Services #9

Open nattapong99 opened 6 years ago

nattapong99 commented 6 years ago

Symfony Service Container: Using XML or YAML to describe Services

จากบทความที่แล้วของ Dependency Injection คุณได้เรียนรู้วิธีการกำหนด service ด้วย PHP code โดยใช้คลาส sfServiceContainerBuilder มาวันนี้ ด้วยตัวช่วยเหลือของ service loaders และ dumpers คุณจะได้เรียนรู้วิธีการกำหนด service ในรูปแบบของ XML และ YAML

โดย Dependency Injection component ของ Symfony มีคลาสช่วยเหลือในการโหลด service โดยใช้ "loader object" โดยค่าเริ่มต้น component มีสองส่วนได้แก่ sfServiceContainerLoaderFileXml ใช้เพื่อโหลด XML files และ sfServiceContainerLoaderFileYaml ใช้เพื่อโหลด YAML files

ก่อนที่จะไปต่อในเรื่อง XML และ YAML notataions ลองพิจารณาส่วนอื่นของ Dependency Injection component คือ "dumper object" ซึ่งเป็น service dumper ที่จะนำเอา container object มาแปลงให้อยู่ในรูปแบบอื่น โดย component นี้มาพร้อมกับ bundle ซึ่งมี dumper สำหรับรูปแบบ XML และ YAML

เพื่อแนะนำให้รู้จัก XML Format เราจะนำ container service มาแปลงให้อยู่ในรูปแบบ container.xml โดยการใช้คลาส sfServiceContainerDumperXml

ลองดูตัวอย่างการแปลง Zend_Mail service

01

เพื่อแปลง container นี้ให้เป็นรูปแบบ XML ทำได้โดยใช้โค้ดนี้

02

โดย constructor ของคลาส dumper ต้องการ service container builder object เป็นอาร์กิวเมนต์ตัวแรก จากนั้นเมธอด dump() จะแปลง container service ไปเป็นรูปแบบที่เราต้องการ ถ้าทุกอย่างเป็นปกติ ไฟล์ container.xml ควรจะมีรูปร่างหน้าตาดังโค้ดข้างล่าง

[xml]
<?xml version="1.0" ?>

<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="mailer.username">foo</parameter>
    <parameter key="mailer.password">bar</parameter>
    <parameter key="mailer.class">Zend_Mail</parameter>
  </parameters>
  <services>
    <service id="mail.transport" class="Zend_Mail_Transport_Smtp" shared="false">
      <argument>smtp.gmail.com</argument>
      <argument type="collection">
        <argument key="auth">login</argument>
        <argument key="username">%mailer.username%</argument>
        <argument key="password">%mailer.password%</argument>
        <argument key="ssl">ssl</argument>
        <argument key="port">465</argument>
      </argument>
    </service>
    <service id="mailer" class="%mailer.class%">
      <call method="setDefaultTransport">
        <argument type="service" id="mail.transport" />
      </call>
    </service>
  </services>
</container>

รูปแบบ XML รองรับการใช้ service ที่ไม่ระบุชื่อ โดย service ที่ไม่ระบุชื่อ คือ service ที่ไม่จำเป็นต้อง มีชื่อและไม่จำเป็นต้องกำหนดบริบทในการใช้งาน ซึ่งมันจะมีความสะดวกมากเมื่อคุณจำเป็นต้องใช้ service ที่ ไม่สามารถใช้งานได้นอก scope ของมัน

[xml]
<service id="mailer" class="%mailer.class%">
  <call method="setDefaultTransport">
    <argument type="service">
      <service class="Zend_Mail_Transport_Smtp">
        <argument>smtp.gmail.com</argument>
        <argument type="collection">
          <argument key="auth">login</argument>
          <argument key="username">%mailer.username%</argument>
          <argument key="password">%mailer.password%</argument>
          <argument key="ssl">ssl</argument>
          <argument key="port">465</argument>
        </argument>
      </service>
    </argument>
  </call>
</service>

การโหลด container.xml ทำได้โดยใช้คลาส XML service loader

03

เช่นเดียวกับ dumper โดย constructor ของคลาส loader ก็ต้องการ service container builder เป็นอาร์กิวเมนต์ตัวแรกเหมือนกัน และเมธอด load() จะอ่านไฟล์และลงทะเบียน service ไปใน container นั้นจึงทำให้ container สามารถใช้งานได้

ถ้าคุณเปลี่ยน dumper code ไปใช้คลาส sfServiceContainerDumperYaml คุณก็จะได้ service มาเป็นรูปแบบ YAML เช่นเดียวกัน

04

ตัวอย่าง container ในรูปแบบ YAML

[yml]
parameters:
  mailer.username: foo
  mailer.password: bar
  mailer.class:    Zend_Mail

services:
  mail.transport:
    class:     Zend_Mail_Transport_Smtp
    arguments: [smtp.gmail.com, { auth: login, username: %mailer.username%, password: %mailer.password%, ssl: ssl, port: 465 }]
    shared:    false
  mailer:
    class: %mailer.class%
    calls:
      - [setDefaultTransport, [@mail.transport]]

การใช้ XML format ช่วยให้คุณได้รับประโยชน์มากกว่าการใช้ YAML format

  • เมื่อไฟล์ XML ได้โหลดเสร็จสิ้น มันจะมีการ validate โดยอัตโนมัติ ด้วยไฟล์ services.xsd

  • XML สามารถ auto-completed ได้ใน IDEs

  • XML format เร็วกว่า YAML

  • XML format ไม่มีการใช้ dependencies ภายนอก (YAML format ใช้ sfYAML component)

คุณสามารถผสมและจับคู่การใช้ loader และ dumper เพื่อแปลงรูปแบบหนึ่ง ๆ ไปสู่อีกรูปแบบหนึ่งได้ ดังตัวอย่าง

05

หนึ่งในสิ่งที่สำคัญที่สุดคือ ความสามารถในการ import "resource" อื่น ๆ โดยที่ resource สามารถเป็นไฟล์การกำหนดค่าใด ๆ ก็ได้

[xml]
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="default.xml" />
  </imports>
  <parameters>
    <!-- These parameters override the ones defined in default.xml -->
  </parameters>
  <services>
    <!-- These service definitions override the ones defined in default.xml -->
  </services>
</container>

ในส่วนของการ import คือรายการของ resource ที่ต้องการนำเข้ามาก่อนที่จะให้ส่วนอื่นประมวลผล โดยค่าเริ่มต้น มันจะค้นหาไฟล์ที่มี path ตรงกับไฟล์นั้น ๆ นอกจากนี้คุณยังสามารถส่งอาเรย์ของ path ไปในอาร์กิวเมนต์ที่สองของ loader ได้

06

คุณสามารถ import ไฟล์ YAML ลงไปใน XML ได้โดยการระบุคลาสที่จะใช้โหลด resource

[xml]
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="default.yml" class="sfServiceContainerLoaderFileYaml" />
  </imports>
</container>

ในทำนองเดียวกัน การ import ไฟล์ XML ในรูปแบบ YAML ก็สามารถทำได้เช่นกัน

[yml]
imports:
  - { resource: default.xml, class: sfServiceContainerLoaderFileXml }

การ import นี้ช่วยให้คุณมีความยืดหยุ่นในการจัดระเบียบ service ของคุณ ซึ่งนี่คือวิธีการที่ดีในการแชร์และนำข้อกำหนดของอื่น ๆ ของ service กลับมาใช้ใหม่ ลองนึกถึงตัวอย่าง web session ที่ได้ยกตัวอย่างไปในบทความแรก เมื่อคุณใช้ web session บน test environment ตัว session storage object อาจต้องการการจำลองเพื่อทดสอบ ในทางตรงกันข้ามถ้าคุณมี load-balanced ของ web servers หลายตัว บน production environment จำเป็นต้องเก็บ session ไว้ในฐานข้อมูล เช่น MySQL เป็นต้น ทางเดียวที่จะกำหนดค่า config ให้มีความแตกต่างกันเป็นค่าเริ่มต้นสำหรับ environment ต่าง ๆ คือการสร้างไฟล์ config ที่แตกต่างกันหลาย ๆ ไฟล์แล้วค่อย import เข้ามาใช้ ดังตัวอย่าง

[xml]
<!-- in /framework/config/default/session.xml -->
<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="session.class">sfSessionStorage</parameter>
  </parameters>

  <!-- service definitions go here -->
</container>

<!-- in /project/config/session_test.xml -->
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="session.xml" />
  </imports>

  <parameters>
    <parameter key="session.class">sfSessionTestStorage</parameter>
  </parameters>
</container>

<!-- in /project/config/session_prod.xml -->
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="session.xml" />
  </imports>

  <parameters>
    <parameter key="session.class">sfMySQLSessionStorage</parameter>
  </parameters>
</container>

ดังนั้นการใช้ค่า config ที่ต้องการก็จะเป็นเรื่องง่าย

07

ผู้คนทั่วไปอาจหนักใจกับการ config ในรูปแบบ XML เนื่องจาก XML อาจไม่ใช่รูปแบบของการ config ที่สามารถอ่านเข้าใจได้ง่าย ใน Symfony นั้นคุณอาจเขียนไฟล์ทั้งหมดในรูปแบบ YAML แต่คุณก็สามารถแยกแยะการกำหนด service จากการ config ได้เช่นกันเนื่องจากคุณสามารถ import ไฟล์รูปแบบอื่น ๆ ได้, คุณสามารถกำหนด service ได้ในไฟล์ services.xml และจัดเก็บการ config ที่เกี่ยวข้องไว้ในไฟล์ parameter.xml รวมถึงคุณยังสามารถกำหนดพารามิเตอร์ในไฟล์ YAML (parameter.yml) และสุดท้าย มีเครื่องมือ built-in INI loader ที่สามารถอ่านพารามิเตอร์จากไฟล์มาตรฐาน INI ได้

[xml]
<!-- in /project/config/session_test.xml -->
<container xmlns="http://symfony-project.org/2.0/container">
  <imports>
    <import resource="config.ini" class="sfServiceContainerLoaderFileIni" />
  </imports>
</container>

<!-- /project/config/config.ini -->
[parameters]
session.class = sfSessionTestStorage

ไม่สามารถกำหนด service ในไฟล์ INI ได้ โดยสามารถกำหนดและวิเคราะห์ได้เฉพาะพารามิเตอร์เท่านั้น

ตัวอย่างเหล่านี้เป็นแค่เรื่องผิวเผินของ container loader และ dumpers features แต่หวังว่าในบทความนี้จะทำให้มองเห็นภาพรวมของรูปแบบ XML และ YAML ที่มีประสิทธิภาพมากกว่ารูปแบบ PHP สำหรับใครที่สงสัยเกี่ยวกับประสิทธิภาพของ container ในการโหลดไฟล์หลาย ๆ ไฟล์เพื่อ config เราจะพูดถึงในบทความถัดไป

source