Posted on by

0 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! :)

Posted on by

0 comment

Il n'y a pas (ou très peu) de documentation sur comment utiliser vos propres patches sur des packages que vous installez avec emerge sous Gentoo, mais sachez que l'on peut le faire!
En théorie, il suffit d'ajouter votre patch au bon endroit, et emerge va le lire et l'appliquer lors de l'éxécution du src_prepare(). Il y a cependant des exceptions.

La fonction epatch_user

C'est la fonction epatch_user (déclarée dans /usr/portage/eclass/eutils.eclass) qui fait tout le travail. Elle doit être appelée dans le ebuild durant l'exécution du src_prepare.
Elle parcours le dossier /etc/portage/{category}/{package}/ et lance epatch pour appliquer tous les patches (ie tous les fichiers) qui s'y trouvent!
Par conséquent, pour que vous puissiez utiliser cette fonctionnalité, l'ebuild doit être configuré de telle sorte qu'il appelle la fonction epatch_user

Ajouter notre patch

Il suffit d'ajouter le patch dans le répertoire /etc/portage/patches/{category}/{package}, où category est la catégorie de votre package (dev-lang pour PHP par exemple) et package le nom de votre package, avec ou sans numéro de version.
Par exemple, si vous avez un patch xxx.patch pour PHP, copiez-le simplement dans le répertoire /etc/portage/patches/dev-lang/php/, et lorsque emerge préparera la source du ebuild, il appliquera votre patch automatiquement!

:)