Webb-On-The-Webb.com

Just another WordPress site
  • About Me
  • Resume
RSS

Control Panel

  • Register
  • Recover password
Jul01

Using the PHP Soap Extension to create a Web Service in a Symfony2 Controller

by Roger Webb on July 1st, 2011 at 3:40 pm
Posted In: Uncategorized

I struggled for awhile to find a way to create a Web Service in an SF2 Controller. The problem I had with NuSOAP was it’s ability to only call static methods and the obscure syntax it forced on the method names on the client side.

There is also a “WebServiceBundle” that is broken in the current release of SF2 (RC3) on symfony2bundles and I, having not hacked around with SF2′s annotation system, was not in a position to fix.

I ended up using PHP’s SOAP Extension and things worked beautifully and simply. The only drawback is that, from what I can tell, PHP SOAP will not generate a WSDL for you. I just made a dummy nusoap_server and registered a method ‘hello’ with it an let nusoap generate a WSDL that I used whole-sale for this example.

class XmlPostServiceController extends Controller {

    public function indexAction() {
        $server = new \SoapServer('/path/to/hello.wsdl');

        $server->setObject($this);

        $response = new Response();

        $response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');

        ob_start();

        $server->handle();

        $response->setContent(ob_get_contents());

        ob_clean();

        return $response;
    }

    public function hello($name) {
        $em = $this->get('doctrine')->getEntityManager();

        $user = $em->createQuery('SELECT u FROM Entity:Users u WHERE u.username = :uname')->setParameter('uname', $name)->getSingleResult();

        return 'Hello, ' . $name . '! Your user_id is ' . $user->getUserId() . '.';
    }

}

You may have noticed the “ob_start”, “ob_get_contents”, and “ob_clean” function calls. The “service” method of SoapServer echos it’s output, but we need to return a Response object. These “ob_” functions are for “output buffering”. After ob_start(), all echoed messages are trapped in the output buffer, which we dump in as the “content” of our Response.

You also want to be sure to set the “Content-Type” header of the response to “text/xml” as that is what the client will expect.

Below is a client-side example using nusoap (replace URL with our URL and route).

$client = new nusoap_client('http://example.com/app.php/webservice?wsdl', true);

$result = $client->call('hello', array('name' => 'World'));

Hope this helps.

Roger

└ Tags: php, SOAP, symfony2, Web Service
6 Comments
May22

Symfony2 Forms – The AJAX City/State Select

by Roger Webb on May 22nd, 2011 at 5:37 pm
Posted In: Uncategorized

(I am operating under the assumption that the reader is already
familiar with the Symfony2 Form Component and Doctrine.
If not, you can read up on it here. )

The concept is simple. We have two HTML SELECT fields, one for
the city and one for the state (US). When the state is changed,
we would like to populate the city select with cities from the
selected state. We’ll use my ContactInfoType as an example.The
form type would look like this:

public function buildForm(FormBuilder $builder, array $options) {
    $builder->add('city', 'entity', array('query_builder' => function (EntityRepository $er) { ... }));
    $builder->add('state', 'entity', array('query_builder' => function (EntityRepository $er) { ... }));
}

Here is the relevant snippet from the Controller:

$em = $this->get('doctrine.orm.entity_manager');

// In beta2 the Entity Manager is retrieved by $this->get('doctrine')->getEntityManager();

$contact = $em->getRepository('Entity:ContactInfo')
 ->find(1);   $form = $this->get('form.factory')
 ->create(new ContactInfoType(), $contact)
 ->getForm();

if($this->get('request')->getMethod() == 'POST') {
    $form->bindRequest($this->get('request');

    $em->persist($contact);
    $em->flush();
}

Everything looks straightforward enough, but the solution is
flawed. Let’s say out $contact’s city is Columbia, MO. When
we instantiate our form in the Controller, the “city” field is
populated with cities in Missouri. Now we want to set the
city to Boulder, CO. We issue out our AJAX request and
retrieve a list of cities in Colorado and populate the “city”
SELECT accordingly, select Boulder and submit the form.
Now, back in our Controller, we reload our $contact form
the database and instantiate our form. Once again, our
“city” field is populated with cities in Missouri. Since this is
now a POST request, we want to bind the request to the
form. When the Form Component tries to setCity, the city
it is trying to set is not on the list, and that’s a problem.
I posted my issue on the Symfony Users mailing list, and
Berhard Schussek was kind enough to offer this solution.
It’s not the shortest solution in the world, but it works. To
work around this we need to use the Event System, utilizing
the “preSetData” and “preBind” events, and create a Closure
to use as a callback to create and populate the “city” field.
Before the data is set, we populate our “city” field with
cities in Missouri and then, before we bind data, we
re-create our “city” field, populating it with cities from
Colorado.
Here is our Closure:

$refreshCity = function ($form, $state) use ($factory) {
    $form->add($factory->createNamed('entity', 'city', null, array(
        'class'         => 'Entity:Cities',
        'property'      => 'city_name',
        'label'         => 'City',
        'query_builder' => function (EntityRepository $repository) use ($state) {
                               $qb = $repository->createQueryBuilder('cities')
                                                ->select(array('cities', 'zip_codes'))
                                                ->innerJoin('cities.states', 'states')
                                                ->innerJoin('cities.zip_codes', 'zip_codes');

                               if($state instanceof States) {
                                   $qb = $qb->where('cities.states = :state')
                                            ->setParameter('state', $state);
                               } elseif(is_numeric($state)) {
                                   $qb = $qb->where('states.state_id = :state_id')
                                            ->setParameter('state_id', $state);
                               } else {
                                   $qb = $qb->where('states.state_id = 1');
                               }

                               return $qb;
                           }
         )));
};

And now, let’s put $refreshCity to use:

$builder->addEventListener(Events::preSetData, function (DataEvent $event) use ($refreshCity) {
 $form = $event->getForm();
 $data = $event->getData();

 if($data == null)
    return;  //As of beta2, when a form is created setData(null) is called first

 if($data instanceof ContactInfo) {
 $refreshCity($form, $data->getCity()->getState());
 }
 });

$builder->addEventListener(Events::preBind, function (DataEvent $event) use ($refreshCity) {
 $form = $event->getForm();
 $data = $event->getData();

 if(array_key_exists('state', $data)) {
 $refreshCity($form, $data['state']);
 }
});

Now we can use the ContactInfoType in our Controller and we
get the result we expect. This may be a somewhat lengthy
solution, but Symfony provides lots of opportunities for code
re-use. I use this form type in a *lot* of places and copy and
paste elsewhere.

└ Tags: ajax, php, symfony2
7 Comments

©2011 Webb-On-The-Webb.com | Powered by WordPress with Easel | Subscribe: RSS | Back to Top ↑