How to Create a SOAP Web Service in a Symfony Controller

Setting up a controller to act as a SOAP server is aided by a couple tools. Those tools expect you to have the PHP SOAP extension installed. As the PHP SOAP extension cannot currently generate a WSDL, you must either create one from scratch or use a 3rd party generator.

SOAP サーバーとして機能するようにコントローラーをセットアップするには、カップルツールが役立ちます。これらのツールでは、PHP SOAP 拡張機能がインストールされている必要があります。現在、PHP SOAP 拡張機能は WSDL を生成できないため、最初から作成するか、サード パーティのジェネレーターを使用する必要があります。

Note

ノート

There are several SOAP server implementations available for use with PHP. Laminas SOAP and NuSOAP are two examples. Although the PHP SOAP extension is used in these examples, the general idea should still be applicable to other implementations.

PHP で使用できる SOAP サーバーの実装がいくつかあります。ラミナ SOAP と NuSOAP は 2 つの例です。これらの例では PHP SOAP 拡張機能が使用されていますが、一般的な考え方は他の実装にも適用できるはずです。

SOAP works by exposing the methods of a PHP object to an external entity (i.e. the person using the SOAP service). To start, create a class - HelloService - which represents the functionality that you'll expose in your SOAP service. In this case, the SOAP service will allow the client to call a method called hello, which happens to send an email:

SOAP は、PHP オブジェクトのメソッドを外部エンティティ (つまり、SOAP サービスを使用する人) に公開することによって機能します。まず、クラス (HelloService) を作成します。これは、SOAP サービスで公開する機能を表します。この場合、SOAP サービスは、クライアントが hello というメソッドを呼び出すことを許可し、たまたま電子メールを送信します。
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
// src/Service/HelloService.php
namespace App\Service;

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class HelloService
{
    private MailerInterface $mailer;

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

    public function hello(string $name): string
    {
        $email = (new Email())
            ->from('admin@example.com')
            ->to('me@example.com')
            ->subject('Hello Service')
            ->text($name.' says hi!');

        $this->mailer->send($email);

        return 'Hello, '.$name;
    }
}

Next, make sure that your new class is registered as a service. If you're using the default services configuration, you don't need to do anything!

次に、新しいクラスがサービスとして登録されていることを確認します。デフォルトのサービス構成を使用している場合は、何もする必要はありません!

Finally, below is an example of a controller that is capable of handling a SOAP request. Because index() is accessible via /soap, the WSDL document can be retrieved via /soap?wsdl:

最後に、SOAP リクエストを処理できるコントローラーの例を以下に示します。 /soap を介して index() にアクセスできるため、/soap?wsdl を介して WSDL ドキュメントを取得できます。
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
// src/Controller/HelloServiceController.php
namespace App\Controller;

use App\Service\HelloService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HelloServiceController extends AbstractController
{
    #[Route('/soap')]
    public function index(HelloService $helloService, Request $request)
    {
        $soapServer = new \SoapServer('/path/to/hello.wsdl');
        $soapServer->setObject($helloService);

        $response = new Response();

        ob_start();
        $soapServer->handle($request->getContent());
        $response->setContent(ob_get_clean());

        foreach (headers_list() as $header) {
            $header = explode(':', $header, 2);
            $response->headers->set($header[0], $header[1]);
        }
        header_remove();

        return $response;
    }
}

Take note of the calls to ob_start() and ob_get_clean(). These methods control output buffering which allows you to "trap" the echoed output of $server->handle(). This is necessary because Symfony expects your controller to return a Response object with the output as its "content". So, you use ob_start() to start buffering the STDOUT and use ob_get_clean() to dump the echoed output into the content of the Response and clear the output buffer. Since $server->handle() can set headers it is also necessary to "trap" these. For this we use headers_list which provides the set headers, these are then parsed and added into the Response after which header_remove is used to remove the headers and to avoid duplicates. Finally, you're ready to return the Response.

ob_start() と ob_get_clean() の呼び出しに注意してください。これらのメソッドは、$server->handle() のエコーされた出力を「トラップ」できるようにする出力バッファリングを制御します。これが必要なのは、Symfony がコントローラーが出力を「内容」として応答オブジェクトを返すことを期待しているためです。そのため、ob_start() を使用して STDOUT のバッファリングを開始し、ob_get_clean() を使用してエコーされた出力を応答の内容にダンプし、出力バッファ。 $server->handle() はヘッダーを設定できるため、これらを「トラップ」する必要もあります。このために、設定されたヘッダーを提供する headers_list を使用します。これらは解析され、応答に追加されます。その後、header_remove を使用してヘッダーを削除し、重複を回避します。最後に、応答を返す準備が整いました。

Below is an example of calling the service using a native SoapClient client. This example assumes that the index() method in the controller above is accessible via the route /soap:

以下は、ネイティブの SoapClient クライアントを使用してサービスを呼び出す例です。この例では、上記のコントローラーの index() メソッドがルート /soap 経由でアクセス可能であることを前提としています。
1
2
3
$soapClient = new \SoapClient('http://example.com/index.php/soap?wsdl');

$result = $soapClient->__soapCall('hello', ['name' => 'Scott']);

An example WSDL is below.

WSDL の例を以下に示します。
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
<?xml version="1.0" encoding="ISO-8859-1"?>
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:tns="urn:helloservicewsdl"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    targetNamespace="urn:helloservicewsdl">

    <types>
        <xsd:schema targetNamespace="urn:hellowsdl">
            <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
            <xsd:import namespace="http://schemas.xmlsoap.org/wsdl/"/>
        </xsd:schema>
    </types>

    <message name="helloRequest">
        <part name="name" type="xsd:string"/>
    </message>

    <message name="helloResponse">
        <part name="return" type="xsd:string"/>
    </message>

    <portType name="hellowsdlPortType">
        <operation name="hello">
            <documentation>Hello World</documentation>
            <input message="tns:helloRequest"/>
            <output message="tns:helloResponse"/>
        </operation>
    </portType>

    <binding name="hellowsdlBinding" type="tns:hellowsdlPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="hello">
            <soap:operation soapAction="urn:arnleadservicewsdl#hello" style="rpc"/>

            <input>
                <soap:body use="encoded" namespace="urn:hellowsdl"
                    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>

            <output>
                <soap:body use="encoded" namespace="urn:hellowsdl"
                    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>
    </binding>

    <service name="hellowsdl">
        <port name="hellowsdlPort" binding="tns:hellowsdlBinding">
            <soap:address location="http://example.com/index.php/soap"/>
        </port>
    </service>
</definitions>