Posted on by

3 comments

For testing purpose, you may want to install a Docker server on an OVH VPS. This short article explain you how to make Docker running on a VPS with an Ubuntu 14.04 system (this may be the same for Debian users).

First of all, as we need to use an official kernel, do not use VPS Classic. Only the VPS Cloud that is running on VMWare let you use another kernel instead of there customized one.

Update your kernel

The first thing to do is to install the default Linux kernel and its extra that enable the AUFS storage driver. Else, Docker will use devicemapper that don't works well on these VPS.

$ apt-get install linux-image-extra-virtual

After installing that new kernel, we need to replace the OVH kernel order in the grub boot sequence to use the newly installed kernel first.

$ mv /etc/grub.d/06_OVHkernel /etc/grub.d/25_OVHkernel
$ update-grub

Reboot your VPS from the command or the manager. Then, you can check the AUFS support, the docker info result should contain:

$ docker info
[...]
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 0
[...]

Fix DNS resolution

When trying to launch a container, you should have the same error as bellow.

# docker run -t -i ubuntu /bin/bash
Unable to find image 'ubuntu:latest' locally
Pulling repository ubuntu
FATA[0000] Get https://index.docker.io/v1/repositories/library/ubuntu/images: dial tcp: lookup index.docker.io: no DNS servers

The simple reason is that your /etc/resolv.conf file is empty, there's no configured DNS servers. I recommend you to remove these packages and set the resolv.conf file manualy:

aptitude remove bind9 resolvconf

In the /etc/resolv.conf, copy/paste these nameservers definitions or use your own, we just need this file to be filled.

nameserver 127.0.0.1
nameserver 8.8.8.8
nameserver 8.8.4.4

Now, you should be able to run any Docker container on your VPS !

Posted on by

1 comment

If you're using the AFNetworking library to perform HTTP queries in an iOS or OS X application, either with Objective-C or Swift, there's no simple way to get the response content in case of failure.

Let's asume the following POST query that send and receive JSON:

let manager = AFHTTPSessionManager(baseURL: NSURL("http://frog-api.localhost"))
manager.requestSerializer = AFJSONRequestSerializer()
manager.responseSerializer = AFJSONResponseSerializer()
 
let params = [
    "foo": "bar"
]
 
manager.POST("/app_dev.php/oauth/v2/token", parameters: params, success: {
    (task: NSURLSessionDataTask!, responseObject: AnyObject!) in
        println("success")
 
}, failure: {
    (task: NSURLSessionDataTask!, error: NSError!) in
        println("error")
})

We'll now talk about the failure listener, and how to get the response status code and then the data.

Get the status code

The NSURLSessionDataTask object as a response attribute which is a NSHTTPURLResponse object. The status code, which is stored in the statusCode attribute, can simply be accessible this way:

let response = task.response as NSHTTPURLResponse
println(response.statusCode)

Get the response content

Getting the response content is a little bit more complicated: we have to create our own response serializer. In this case, we'll override the responseObjectForResponse method of the AFJSONResponseSerializer.

The following implementation simply add the result data in the userInfo of the error object which is accessible in the failure closure:

class JSONResponseSerializer: AFJSONResponseSerializer
{
    override func responseObjectForResponse (response: NSURLResponse, data data: NSData, error error: AutoreleasingUnsafePointer<NSError?>) -> AnyObject
    {
        var json = super.responseObjectForResponse(response, data: data, error: error) as AnyObject
 
        if error.memory? {
            var errorValue = error.memory!
            var userInfo = errorValue.userInfo as NSDictionary
            var copy = userInfo.mutableCopy() as NSMutableDictionary
 
            copy["data"] = json
 
            error.memory = NSError(domain: errorValue.domain, code: errorValue.code, userInfo: copy)
        }
 
        return json
    }
}

Then, you've to change the response serializer you want to use with your AFNetworking manager:

manager.responseSerializer = JSONResponseSerializer()

Based on that we can access to the response content using the error's userInfo:

if let data = error.userInfo["data"] as? NSDictionary {
    println(data)
}

Posted on by

1 comment

Using JMSSerializer (or its bundle) serialized field are renamed by default to the underscore-separated words naming convention. If like me you prefer camel-case naming and you want fields serialized as it, there's two solutions.

1. Set serialize name for each fields

You can use the @SerializedName annotation, serialized-name XML property or the serialized_name YAML configuration to fix the name that will be used for each fields. 

2. Change the whole naming strategy

If like me you don't want to set the serialized name for each field, you can change the naming strategy by your custom one or the simple IdenticalPropertyNamingStrategy that don't rename fields.

The first solution is to set the naming strategy while creating your serializer using the SerializerBuilder:

return SerializerBuilder::create()
            ->setPropertyNamingStrategy(new SerializedNameAnnotationStrategy(new PassThroughNamingStrategy()))
            ->build();

The second solution if you use the bundle is to override the jms_serializer.camel_case_naming_strategy.class parameter in your app/config/parameters.yml file.

parameters:
    jms_serializer.camel_case_naming_strategy.class: JMS\Serializer\Naming\IdenticalPropertyNamingStrategy

Posted on by

1 comment

If you're using Sonata Admin and Symfony's ACL, you can extends Sonata Admin with the CoopTilleulsAclSonataAdminExtensionBundle. Using it, lists just contain data the logged in user has right to view. Thanks to the ACL editor, you can simply manage ACL inside your application and in the Sonata Admin.

It's perfectly working when objects are created inside the Sonata Admin, but don't works when you're creating them in a custom controller. 
To make it working, you just have to create the ACL using the Sonata Admin Security Handler.

Let's imagine you've a controller that is handling a form, and we've like the following code:

// src/Foo/BarBundle/Controller/ModelController.php
class ModelController extends Controller 
{
    public function handleAction () 
    {
        // ...
        if ($form->isValid()) {
            $model = $form->getData();
 
            // Persist and flush your model, and do anything like
            // redirect.
        }
        // ...
    }
}

Now, you just have to get the sonata.admin.security.handler which is an alias of the current security handler (look at SonataAdminExtension.php#L108) and update ACLs after persisting your model. If your Admin service for this model is named foo.barbundle.admin.model, use the following code:

// Create ACLs
$adminSecurityHandler = $this->container->get('sonata.admin.security.handler');
$modelAdmin = $this->container->get('foo.barbundle.admin.model');
 
$objectIdentity = ObjectIdentity::fromDomainObject($model);
$acl = $adminSecurityHandler->getObjectAcl($objectIdentity);
if (is_null($acl)) {
    $acl = $adminSecurityHandler->createAcl($objectIdentity);
}
$adminSecurityHandler->addObjectClassAces($acl, $adminSecurityHandler->buildSecurityInformation($modelAdmin));
$adminSecurityHandler->updateAcl($acl);

Posted on by

0 comment

Even if it's the best idea to write tests that need a real database connection, sometimes it's something needed. If you're using Symfony2, you can simply follow the "How to test Doctrine Repositories" part of Symfony documentation to do that.

But if you're running a large amount of tests you'll get the following MySQL error:

SQLSTATE[08004] [1040] Too many connections

This means that you reached the maximum number of simultanous connections (max_connections parameter in my.cnf). To fix that behaviour, I had to change the processIsolation parameter of PHPUnit in the phpunit.xml file, like this:

<phpunit
    backupGlobals               = "false"
    backupStaticAttributes      = "false"
    colors                      = "true"
    convertErrorsToExceptions   = "true"
    convertNoticesToExceptions  = "true"
    convertWarningsToExceptions = "true"
    processIsolation          = "true"
    stopOnFailure               = "false"
    syntaxCheck                 = "false" 
    bootstrap                   = "bootstrap.php.cache" >

Posted on by

2 comments

From Symfony 2.3 we can use (PR #7849) PATCH requests with the Form component to submit a part of the form which will override the form default values. This is really helpful moreover using FOSRestBundle and its listeners !

With this configuration, to make a user available to PATCH its profile using REST API, you simply have to write this few lines:

    /**
     * @Route("/api/profile", name="api_profile_update")
     * @Method({"PATCH"})
     * @View()
     */
    public function patchAction (Request $request)
    {
        $form = $this->createForm(new ProfileFormType(), $this->getUser());
        $form->handleRequest($request);
 
        if ($form->isValid()) {
            $user = $form->getData();
            $this->get('fos_user.user_manager')->updateUser($user);
            return $user;
        }
 
        return $form;
    }

But the form is never valid...

In fact, you'll never have a valid form if you let it as it's. The problem is that the form is never submitted by the HttpFundationRequestHandler because the current request method (PATCH) isn't a GET or POST, which are waited by the form (see L39).

Two solutions

You've 2 simple solutions, use the submit method, or set the method option on the form.

// src/My/Bundle/Form/Type/ProfileFormType.php
...
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            // ...
            'method' => 'PATCH'
        ));
    }
...
    public function patchAction (Request $request)
    {
        $form = $this->createForm(new ProfileFormType(), $this->getUser());
        $form->submit($request->request->all());
        // ...

Posted on by

0 comment

Dans un article précédent, j'expliquais comment patcher un package Gentoo via le répertoire /etc/portage/patches/. Cependant, pour utiliser cette méthode, il faut que l'ebuild appelle la fonction epatch_user dans le src_prepare.

Lorsque ce n'est pas le cas, vous ne pouvez donc pas utiliser cette méthode. Pour cela, vous pouvez créer votre propre ebuild à partir du tree officiel dans votre portage overlay.

Configurer le portage overlay

Pour configurer le portage overlay, il vous suffit d'ajouter la ligne suivante dans le fichier /etc/make.conf:

PORTDIR_OVERLAY="/usr/local/portage"

Ainsi, votre overlay est situé dans le dossier /usr/local/portage.

Modifier un ebuild

Si par exemple, vous voulez modifier le package dev-php/xdebug, il vous suffit de copier le fichier ebuild dans votre package overlay:

mkdir -p /usr/local/portage/dev-php/xdebug/
cp /usr/portage/dev-php/xdebug/xdebug-2.1.2.ebuild /usr/local/portage/dev-php/xdebug/xdebug-2.1.2.ebuild

Il vous suffit d'ajouter la fonction post_src_prepare pour y appeler la fonction epatch_user comme ceci:

post_src_prepare () {
    epatch_user
}

Ensuite, il faut générer le fichier Manifest grâce à l'outil ebuild:

cd /usr/local/portage/dev-php/xdebug/
ebuild xdebug-2.1.2.ebuild manifest

Maintenant, emerge va tout d'abord récupérer le package depuis vote overlay, et va donc appliquer les patches de votre dossier /etc/portage/patches/dev-php/xdebug/.

Posted on by

0 comment

Si vous tombez sur l'erreur TypeError: must be type, not classobj en Python en essayant d'appeler la méthode super(), c'est très probablement que vous avez oublié de définir vos classes en new-styled.

En effet, si vous avez défini vos classes de cette manière, vous aurez l'erreur:

class X:
   def a(self):
     print "a"
 
class Y(X):
   def a(self):
     super(Y,self).a()
     print "b"

Pour les définir en new-styled, il vous suffit d'ajouter object comme classe parent de la première. Ainsi, votre code corrigé est:

class X(object):
   def a(self):
     print "a"
 
class Y(X):
   def a(self):
     super(Y,self).a()
     print "b"

Et tout fonctionne! :)