Da der native PHP SoapServer keine Schemavalidierung out-of-the-box unterstützt und ich zu dem Thema wenige praktische Beispiele gefunden habe zeige ich in diesem Artikel wie man dies dennoch mit wenigen Zeilen Code erreicht.
Wir haben folgenden Webservice:
MyService.wsdl
<?xml version ="1.0" encoding ="UTF-8" ?> <definitions name='myservice' targetNamespace='http://mydomain/myservice' xmlns:tns='http://mydomain/myservice' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:myxsd='http://mydomain/myservice/schema'> <import location="http://mydomain/myservice/MyService.xsd" namespace="http://mydomain/myservice/schema"/> <message name='MyServiceRequest'> <part name='ID' element="myxsd:ID" xsd:use='required'/> <part name='Gender' element="myxsd:SomeParam" xsd:use='required'/> </message> <message name='MyServiceResponse'> <part name='info' type='xsd:string'/> </message> <portType name='MyServicePortType'> <operation name='GetMyService'> <input message='tns:MyServiceRequest'/> <output message='tns:MyServiceResponse'/> </operation> </portType> <binding name='MyServiceBinding' type='tns:MyServicePortType'> <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/> <operation name='GetMyService'> <soap:operation soapAction='urn:MyFunction#MyFunction'/> <input> <soap:body use='encoded' namespace='urn:MyFunction' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> </input> <output> <soap:body use='encoded' namespace='urn:MyFunction' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> </output> </operation> </binding> <service name='MyServiceService'> <port name='MyServicePort' binding='MyServiceBinding'> <soap:address location='http://mydomain/myservice/MyService.php'/> </port> </service> </definitions>
Und das dazugehörige Schema MyService.xsd
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.w3.org/2000/10/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://mydomain/myservice/schema" > <xsd:import namespace="http://schemas.xmlsoap.org/soap/envelope/" schemaLocation="http://schemas.xmlsoap.org/soap/envelope/"/> <xsd:element name="ID"> <xsd:simpleType> <xsd:restriction base="xsd:positiveInteger"> <xsd:minInclusive value="1"/> <xsd:maxInclusive value="2147483647"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="Gender"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:pattern value="male|female"/> </xsd:restriction> </xsd:simpleType> </xsd:element> </xsd:schema>
Jetzt möchte ich bei einem Request die Parameter gegen das Schema prüfen bevor er verarbeitet wird. Also ob ID eine positive natürliche Zahl zwische 1 und 2147483647 ist und in Gender entweder "male" oder "female" übergeben wurde.
Das folgende Beispiel tut dies NICHT:
MyService.php
<?php ini_set("soap.wsdl_cache_enabled", "0"); error_reporting( E_ALL ); use_soap_error_handler(true); function MyFunction($ID, $Gender) { global $bOk; ob_start(); var_dump($ID); var_dump($Gender); $var_dump = ob_get_contents(); ob_end_clean(); return $var_dump; } try { $oServer = new SoapServer('http://mydomain/myservice/MyService.wsdl'); $oServer->addFunction("MyFunction"); $oServer->handle($HTTP_RAW_POST_DATA); } catch (SOAPFault $f) { error_log($f->getMessage()); }
Aber mit ein paar Zeilen Code ist dies möglich:
MyService.php
<?php ini_set("soap.wsdl_cache_enabled", "0"); error_reporting( E_ALL ); use_soap_error_handler(true); function MyFunction($ID, $Gender) { global $bOk; ob_start(); var_dump($ID); var_dump($Gender); $var_dump = ob_get_contents(); ob_end_clean(); if($bOk) { return $var_dump; } else { return getValidationErrors(); } } function getValidationErrors() { $oErrors = libxml_get_errors(); libxml_clear_errors(); return print_r($oErrors, 1); } try { $oServer = new SoapServer('http://mydomain/myservice/MyService.wsdl'); $oServer->addFunction("MyFunction"); libxml_use_internal_errors(true); $oDok = new DOMDocument(); $oDok->loadXML($HTTP_RAW_POST_DATA); $bOk = $oDok->schemaValidate('http://mydomain/myservice/MyService.xsd'); $oServer->handle($HTTP_RAW_POST_DATA); } catch (SOAPFault $f) { error_log($f->getMessage()); }
Das Beispiel würde da etwas in dieser Art zurückgeben:
Valider Request: SoapClient->MyFunction(1, 'male')
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:MyFunction" 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/"> <SOAP-ENV:Body> <ns1:GetOrderResponse> <info xsi:type="xsd:string"> int(1) string(4) "male" </info> </ns1:GetOrderResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Invalider Request: SoapClient->MyFunction(-1, 'Foo');
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:MyFunction" 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/"> <SOAP-ENV:Body> <ns1:GetOrderResponse> <info xsi:type="xsd:string">Array ( [0] => LibXMLError Object ( [level] => 2 [code] => 1824 [column] => 0 [message] => Element '{http://mydomain/myservice/schema}ID': '-1' is not a valid value of the local atomic type. [file] => ... [line] => 5 ) [1] => LibXMLError Object ( [level] => 2 [code] => 1839 [column] => 0 [message] => Element '{http://mydomain/myservice/schema}Gender': [facet 'pattern'] The value 'Foo' is not accepted by the pattern 'male|female'. [file] => ... [line] => 6 ) [2] => LibXMLError Object ( [level] => 2 [code] => 1824 [column] => 0 [message] => Element '{http://mydomain/myservice/schema}Gender': 'Foo' is not a valid value of the local atomic type. [file] => ... [line] => 6 ) )</info> </ns1:GetOrderResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Wichtig damit die Valierung funktioniert ist in der MyService.xsd die Zeile
<xsd:import namespace="http://schemas.xmlsoap.org/soap/envelope/"
schemaLocation="http://schemas.xmlsoap.org/soap/envelope/"/>
damit der äußere Wrapper der SOAP Message validiert werden kann bevor die eigenen Regeln geparst werden.
Dieses Beispiel ist nicht in der Form von getestet worden und dient nur als Leitfaden
Das sfDoctrineGuard Plugin für Symfony beschert einem nach der Installation mit allem was man für eine Benutzerverwaltung und Authentifizierung von geschützen Bereichen in einem Symfony Projekt braucht. Allerdings gerät man schnell an einige Fälle in der Praxis bei denen man um Erweiterungen und Anpassungen nicht umhin kommt.
Es gibt viele Beispiele im Internet die diesen oder jenen Fall abdecken. Ich bemühme mich in diesem Artikel die häufigsten use cases die mir bisher begegnet sind abzudecken.
Die Basics:
Nach der Installation ist ein erster Test angesagt. Hier für müssen wir das Modul in der settings.yml erst einmal freischalten:
all:
.settings:
enabled_modules: [(...), sfGuardAuth]
login_module: sfGuardAuth
login_action: signin
secure_module: sfGuardAuth
secure_action: secure
Nach dem wir den Cache gelöscht haben weiß das Symfonyframework nun das es das sfGuardAuth Modul des Plugins zur Authentifizierung nutzen soll.
Als nächstes muss die User Klasse von der Application um alle Eigenschaften und Methoden der Klasse sfGuardSecurityUser erweitertet werden:
Ändere myUser.class.php deiner application (myProject/apps/myApp/lib/myUser.class.php) wie folgt
class myUser extends sfGuardSecurityUser { }
Nun kannst du ein Modul (myProject/apps/myApp/modules/myModul/config/security.yml) oder deine ganze Appliktion (myProject/apps/myApp/config/security.yml) zu einem geschützten Bereich machen in dem du in der entsprechenenden security.yml folgendenden entrag machst:
default:
is_secure: on
Wenn du nun den geschützen Bereich deiner Seite aufrufst sollte das Login Formular erscheinen. In den default fixtures des Plugins wird der Superadmin mit user: admin passwort: admin angelegt. Hiermit sollte dir ein login gelingen.
Das Usermodel um eigene (Profil) Felder erweitern
Das sfAuthUser Model enthält folgende Felder:
- username
- algorithm
- salt
- password
- created_at
- last_login
- is_active
- is_super_admin
Es gibt keine Felder für Email, Name, Geburtstag oder was auch immer du für deinen User noch speichern möchtest.
Du solltest diese Änderungen nicht einfach in der schema.yml vornehmen, da diese bei einem Update des Plugins verloren gehen. Statt dessen kannst du deine eigene user-profile Klasse erstellen. Standartmäßig sucht das sfGuardUser Model nach einem assoziierten sfGuardUserProfile Model, welches du in einer eigenen schema.yml selbst definieren kannst:
Hier ist ein simples Beispiel welchens du verwenden oder anpassen kannst:
sfGuardUserProfile:
columns:
id: { type: integer, primary: true, autoincrement: true }
user_id: { type: integer }
firstname: { type: string(255) }
lastname: { type: string(255) }
relations:
User:
class: sfGuardUser
local: user_id
foreign: id
type: one
foreignType: one
foreignAlias: Profile
user_id ist der Fremdschlüssel zu sf_guard_user. Du kannst den Fremdschlüsselname und die assoziierte user-profile Klasse in app.yml ändern:
all:
sf_guard_plugin:
profile_class: sfGuardUserProfile
profile_field_name: user_id
Nun kannst du das User Profil über das User Objekt ansprechen:
$this->getUser()->getGuardUser()->getProfile()->getFirstName(); // or via the proxy method $this->getUser()->getProfile()->getFirstName(); // bzw. außerhalb der action sfContext::getInstance()->getUser()->getProfile()->getFirstName();
Userverwaltung im Backend:
123
Um den Doctrine Model Konstruktor der Base Klasse zu überschreiben wäre die normale Vorgehensweise:
class User extends BaseUser{ public function __construct(){ parent::__construct(); echo $this->id; (...) } }
Zu diesem Zeitpunkt ist das Model allerdings noch nicht voll initialisiert und der Zugriff auf die Eigenschaften des Models z.B. $this->id wie im Beispiel oben führt zu einer Doctrine_Table_Exception.
Zum Glück kann man einfach Doctrines eigenen Konstruktor überschreiben:
class User extends BaseUser{ public function construct() { parent::construct(); echo $this->getId(); (...) } }