Yet Another Programming Blog
PHP, Drupal, Zend Framework. Done.
PHP, Drupal, Zend Framework. Done.
Mon, 15 Aug, 2011
Recently, whilst trying to make an old PHP application W3C compliant, I realised that none of the ampersands separating the GET parameters in the links that the application was generating were written as & - they were all just plain ampersands.
This results in the following error from the validator:
Warning: unescaped & or unknown entity "&id"The solution is pretty straightforward - find all instances in the code where URLs are being generated and replace each ampersand with the proper & notation.
However, in this particular application, there were 502 instances! Although I would be happy with a manual search and replace solution, the problem with just searching for the ampersands is that there are obviously many others in the code that are unrelated to URL generation.
So after a short amount of Googling I came up with the following solution:
Search for: \&+([a-zA-Z]+)
Replace with: &$1This will find all strings that consist of an ampersand followed by some alpha characters and replace the ampersand with the proper code. The good thing is that this way of searching excludes logic operators, single ampersands in comments, etc. The bad thing is that it picks up ampersands at the front of things that are already HTML entities. If anybody knows of a way to exclude those using regular expressions, please feel free to drop a comment below.
Sun, 17 Jul, 2011
It's funny how seemingly trivial things can turn into rather significant undertakings...
Like when a colleage of mine asked a very benign-sounding question over Skype about generating random 11-character strings.
My first response to the question of generating random 11-character strings was to regurgitate some code I found on php.net:
<?php
$str = preg_replace('/([ ])/e', 'chr(mt_rand(97,122))', ' ');
?>When I discovered the basis for the question - to create random YouTube video IDs - I realised I would have to change it slightly:
<?php
$vid = preg_replace('/([ ])/e',
'substr( "0123456789-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", mt_rand(0,62), 1 )', ' ');
?>However, when I ran a script that generated random YouTube links based on these random video IDs, I discovered that I was basically looking for needles in a very, very, very large haystack. That is, most of the video IDs that I generated randomly did not actually exist (the numeric space in those 11-character strings is several orders of magnitude greater than the number of YouTube videos that actually exist).
So that brief Skype chat has turned into a rather interesting side project, the aim of which is to create a database of several thousand truly unique YouTube video IDs.
One of the reasons we want the random video IDs is to power randomyoutube.net. This site was built with Drupal 7 in about 45 minutes (yay Drupal!) and the back-end that does the searching, storing and retrieving of the video IDs uses Zend Framework. Although Zend Framework is probably overkill for our current needs, I must say that the Zend_Gdata_YouTube class definitely makes working with the YouTube API very, very painless.
There are, however, some much more interesting things that can be done with random YouTube video IDs, some of which I'll share with the world in a later post.
Tue, 12 Apr, 2011
After spending two days figuring out how to cleanly call PayPal's SOAP API in the sandbox environment, I was pretty much gutted when I couldn't get the same code working against the production environment.
I just kept getting the same error:
[Errors] => stdClass Object
(
[ShortMessage] => Security error
[LongMessage] => Security header is not valid
[ErrorCode] => 10002
[SeverityCode] => Error
)If you've hit this issue yourself then you're probably sick and tired by now of reading forum post after forum post telling people that this error only occurs when you use the wrong credentials or try to use the right credentials to access the wrong endpoint. Blah blah blah blah blah... yeah, read it a million times... got sick of reading it!
In my case I was following all the rules (or so I thought). I was definitely instantiating my SOAPClient with the URL for the production WSDL and I was definitely using production credentials. I even tried dropping the credentials and re-creating them, as suggested in one or two forum posts, but that didn't help.
I also whipped up some code to do a GetBalance() request in the production environment using the NVP API and that worked, proving beyond a skeric of doubt that my credentials were, indeed, correct.
And I did RTFM too, which, in hindsight, possibly contributed to the problem, because the PHP manual clearly states that you only need to provide the location of the endpoint as one of the options to the SOAPClient constructor if you're running in non-WSDL mode. I guess this makes sense, because the WSDL should contain the correct URL for the endpoint, right?
WRONG!!!!
Let me repeat that...
WRONG!!!
The breakthrough came when, in desperation, I read through all of the comments on the SOAPClient page and discovered a hint in the second last comment, made by a Mr Jim Plush all the way back in 2005. The comment was this:
As of version 5.0.4 you can now dynamically change your location even if you're using a wsdlSo I thought to myself, "What the hell, I'll just explicitly set the location of the endpoint and see if that solves the problem".
And guess what? IT WORKED!!!!
You are not going to believe this, but at the time of writing, the endpoints, as defined in the PRODUCTION Paypal WSDL, are:
<wsdl:service name="PayPalAPIInterfaceService">
<wsdl:port name="PayPalAPI" binding="ns:PayPalAPISoapBinding">
<wsdlsoap:address location="https://api.sandbox.paypal.com/2.0/"/>
</wsdl:port>
<wsdl:port name="PayPalAPIAA" binding="ns:PayPalAPIAASoapBinding">
<wsdlsoap:address location="https://api-aa.sandbox.paypal.com/2.0/"/>
</wsdl:port>
</wsdl:service>Yeah, it says "sandbox" in there. D'oh...
So if you are hitting this issue and you are using the more common signature-type authentication (as opposed to certificate authentication), the code to instantiate your SOAPClient object should look something like this:
<?php
$client = new SoapClient( 'https://www.paypal.com/wsdl/PayPalSvc.wsdl',
array( 'location' => 'https://api-3t.paypal.com/2.0/',
'soap_version' => SOAP_1_1 ));
?>I guess the big lesson in all this - again - is to always double check all assumptions, even those that you wouldn't normally question, when trying to resolve an issue.
I gotta tell you, the idea of lurking on StackOverflow until somebody asked this question again so that I could post the answer and get, like, a bazillion rep points did occur to me, but then I thought to myself, "I was only able to solve this problem due to a hint I got from somebody who was generous enough with their time to post their thoughts on php.net. Therefore, the right thing to do would be to make this information available ASAP so that others can benefit". So I did.
But I'm still going for those rep points if I spot this issue on StackOverflow. :-)