Welcome to Abdul Malik Ikhsan's Blog

Using igorw/retry to handle collision of Uuid

Posted in Tutorial PHP by samsonasik on December 31, 2014

While the Uuid collide probability is almost zero, and only happen when you have a very very very bad luck, there is always a chance for it. If you’re using PHP, I encourage to use igorw/retry! You can retry your process as many as your wish!
Let’s grab it by adding into composer require :

composer require "igorw/retry:dev-master"

And as demo, I’m going to use “rhumsaa/uuid”, we can add again in composer require :

composer require "rhumsaa/uuid:~2.8"

Great!, let's use it :

require 'vendor/autoload.php';

use function igorw\retry;
use Rhumsaa\Uuid\Uuid;

$uuid    = Uuid::uuid4();
$uuidStr = $uuid->toString();

Now, the Uuid instance and its string random generation created, so we can use it in retry.

$i = 0;
// retry maximum : 5 times until succeed
retry(5, function () use ($uuid, $uuidStr, &$i) {

    $i++;

    if ($i > 1) {
        $uuidStr = $uuid->toString();
    }

    // this is pseudo code
    $this->db('user')->insert([
        'id' => $uuidStr,
        'name' => 'foo'
    ]);
});

When your insertion failed and got exception, it will silently ( doesn’t show error message ) and do re-try again with new string generation instead and stop when it succeded. You can change how many retry by changing 1st parameter in retry function.

References :

  1. http://en.wikipedia.org/wiki/Universally_unique_identifier
  2. https://github.com/igorw/retry
  3. https://github.com/ramsey/uuid

Zend Framework 2 : Check request URI matched against registered Router

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on December 28, 2014

zf2-zendframework2 When you develop web application using Zend Framework 2, there is sometime you get the situation that you need to check requested URI matched against registered ‘Router’, for example : you have uri : http://zf2app/register?redirect=/user  which the application will redirect to passed ‘redirect’ parameter. What if user make bypassed and inject unexpected redirect param ? For example, you have code like this in your controller action :

$redirect = $this->params()->fromQuery('redirect','');
return $this->redirect()->toUrl($redirect);

We can inject unwanted redirect into browser, and BOOM, the site will be redirected to unwanted uri. So, we need to handle it! we need to check whenever the ‘redirect’ value can be matched with registered Router.
First, check if the request URI can be matched against registered Router.

$redirect = $this->params()->fromQuery('redirect','');

$request  = $this->getRequest();
$request->setUri($redirect);

// assignment, not Comparison, that's why use single '='
if ($routeToBeMatched = $this->getServiceLocator()->get('Router')->match($request)) {
    // ... process redirection based on 'redirect' param...
}

When request URI matched against registered Router, then we assign it to $routeToBeMatched variable, we can procees it. What if the “redirect route equals current route” ? We can check it under if.

if ($routeToBeMatched = $this->getServiceLocator()->get('Router')->match($request)) {
   // handle if redirect route = current route
   $currentRouteMatchName = $this->getEvent()->getRouteMatch()->getMatchedRouteName();
   if ($routeToBeMatched->getMatchedRouteName() != $currentRouteMatchName) {
       return $this->redirect()->toUrl($redirect);
   }
}

Great! now, handle default redirection when doesnt’ match :

if ($routeToBeMatched = $this->getServiceLocator()->get('Router')->match($request)) {
    // handle if redirect route = current route
    $currentRouteMatchName = $this->getEvent()->getRouteMatch()->getMatchedRouteName();
    if ($routeToBeMatched->getMatchedRouteName() != $currentRouteMatchName) {
        return $this->redirect()->toUrl($redirect);
    }
}
return $this->redirect()->toUrl('/user');

Ok, this is the complete code sample:

$redirect = $this->params()->fromQuery('redirect','');

$request  = $this->getRequest();
$request->setUri($redirect);

if ($routeToBeMatched = $this->getServiceLocator()->get('Router')->match($request)) {
    // handle if redirect route = current route
    $currentRouteMatchName = $this->getEvent()->getRouteMatch()->getMatchedRouteName();
    if ($routeToBeMatched->getMatchedRouteName() != $currentRouteMatchName) {
        return $this->redirect()->toRoute($redirect);
    }
}
return $this->redirect()->toUrl('/user');

Update: There is a module for that that I created, go grab it here: https://github.com/samsonasik/RedirectHandlerModule 😉

Symfony 2.6.1 : Using class_alias of Debug’s FlattenException to work with Error page previews

Posted in Symfony2 Framework, Tutorial PHP by samsonasik on December 19, 2014

Error page previews is one of the new features that land in Symfony 2.6. You should be able to access /_error/{statusCode}.{format}. Unfortunatelly, when I try in Symfony 2.6.1, It doesn’t work. The reason of issue is that in TwigBundle\Controller\ExceptionController::showAction() require HttpKernel’s FlattenException that should be require Debug’s FlattenException.
symfony2-exception-error-preview
The HttpKernel’s FlattenException is deprecated from Symfony 2.3 and should be removed in 3.0.
If you see the Symfony\Bundle\TwigBundle\Controller\ExceptionController, you will see :

use Symfony\Component\HttpKernel\Exception\FlattenException;
// ...
// ...
    public function showAction(
       Request $request,
       FlattenException $exception,
       DebugLoggerInterface $logger = null)
    {

    }
// ...
// ...

So, You just cannot make custom exception that extends the ExceptionController because of the typehint requirement. What we can do ? The issue already reported in github so we just can wait to make it work in next maintenance release – OR – do tricky in the skeleton! Don’t do it in vendor libraries directly. You shouldn’t touch it anymore. We can edit our symfony-standard skeleton application in web/app_dev.php by apply class_alias after loader loaded :

// web/app_dev.php
// ...
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
class_alias(
    'Symfony\Component\Debug\Exception\FlattenException',
    'Symfony\Component\HttpKernel\Exception\FlattenException'
);
// ...

That’s it, so when you try access : http://127.0.0.1:8000/_error/404.html or whatever your Symfony url skeleton application with /_error/404.html, you will get something like this :
symfony2-404-error-preview

Done 😉

Using PHP Phantomjs with Codeception

Posted in Javascript by samsonasik on December 18, 2014

phantomjs logoWhen you do a web acceptance test for web with javascript support, and you want to use phantomjs but doesn’t have root access to install new application, you probably need this, the “jonnyw/php-phantomjs” lib. You can install via composer parallel with “codeception/codeception” by configuring composer.json like the following :
 

{
    "require": {
        "codeception/codeception": "2.*",
        "jonnyw/php-phantomjs": "3.*"
    },
    "scripts": {
        "post-install-cmd": [
            "PhantomInstaller\\Installer::installPhantomJS"
        ],
        "post-update-cmd": [
            "PhantomInstaller\\Installer::installPhantomJS"
        ]
    }
}

and then run :

composer install

Ok, when everything installed, you already have : vendor/bin/phantomjs executable file.

If you add :

   "config": {
        "bin-dir": "bin"
    }

Inside your composer.json, you should have individual bin folder outside the vendor folder so you can run from it like described at http://jonnnnyw.github.io/php-phantomjs/. I personally like it should be inside the vendor instead. You can choose what you prefer.

Next step is start the phantomjs service by run command :

vendor/bin/phantomjs --webdriver=4444

Let’s continue to the sample page, try create a sample html page :

<html>
<body>

   <h1>Hello World!</h1>
 
  <button name="test-btn" id="test-btn">test-btn</button>
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript">
    $(document).ready(function() {
       $("#test-btn").click(function() {
            $("h1").html("abcdef");
       });
    });
  </script> 

</body>
</html>

At this sample page, we want to :

if user go to /index.html, he will see “Hello World!” and don’t see the “abcdef” inside “h1” and then if user click “#test-btn” then he can see “Hello World!” replaced with “abcdef” and see the “abcdef” inside “h1”.

Now, let’s initialize the codecept by open separate console window and run :

vendor/bin/codecept bootstrap

Then, call command :

vendor/bin/codecept generate:cept acceptance Index

We then will get pre-filled configuration and make tests with folder named “tests”. What we need to do, is configure the ‘tests/acceptance.suite.yml’ file like the following :

# Codeception Test Suite Configuration
# filename tests/acceptance.suite.yml
class_name: AcceptanceTester
modules:
    enabled: [WebDriver]
    config:
        WebDriver:
            url: 'http://localhost/codeceptiontest/' # our url base
            browser: firefox
            capabilities:
                unexpectedAlertBehaviour: 'accept'

Great! then we can create a test in ‘tests/acceptance/IndexCept.php’ :

// filename : tests/acceptance/IndexCept.php
$I = new AcceptanceTester($scenario);
$I->wantTo('see home page');
$I->amOnPage('/index.html');
$I->see('Hello World!');
$I->dontSee("abcdef", '//body/h1');
$I->click('#test-btn');
// $I->wait(1); // you can set delay
$I->See('abcdef','//body/h1');

And finally! Run the test!

vendor/bin/codecept run acceptance

run-test-codeception-php-phantomjs

Done 😉

References :
1. http://jonnnnyw.github.io/php-phantomjs/
2. http://codeception.com/docs/modules/WebDriver

Tagged with: