Emre.xyz

php

You need to use a Voter for workflow entity for managing access policies when you use the Workflow component. Workflow component supports access policies with guard option, but the documentation won't point out how to get the subject variable for a voter class.

I faced this situation when I was developing a feature for my last customer. I needed to dig into the workflow component`s code. I found out the class that getting guard config, parsing with expression language, and creating transaction blocker.

This class is evaluating expression with some variables.

$variables = [
            'token' => $token,
            'user' => $token->getUser(),
            'subject' => $event->getSubject(),
            'roles' => $roles,
            'role_names' => $roleNames,
            // needed for the is_granted expression function
            'auth_checker' => $this->authorizationChecker,
            // needed for the is_* expression function
            'trust_resolver' => $this->trustResolver,
            // needed for the is_valid expression function
            'validator' => $this->validator,
        ];

This is our solution, you can use these variables for writing your expression to workflow guard. I needed subject variable and I used to this in transaction configuration:

        to_rejected:
          guard: "is_granted('reject',subject)"
          from: draft
          to: rejected

Now I can use my voter class with full capabilities.

#symfony #workflow #voter #en #php

Composer is an installer and a package management tool. So please stop writing build scripts for Composer, building anything inside your package folder.

Last few days, I try to deploy a Symfony project with Nixops to Google Cloud. I found the composer2nix package after some research.

Composer2nix is a nix-expression builder. It parses composer.lock file and generates nix expressions for every composer packages independently. After that, you can use generated files in your deployment file like that:

  pkgs = import <nixpkgs>{};
  my_app = import /var/www/app/source{
    inherit pkgs;
  };

Nix builds your application and system when you run nixops deploy.

But NixOps generates a hash folder and installs your package inside this stateless and read-only folder. After that, it makes relations all required packages and your project.

composer2nix generated expression works the same way, e.g., your composer.lock file if had theofidry/alice-data-fixtures package. composer2nix generates expression like that:

"theofidry/alice-data-fixtures" = {
      targetDir = "";
      src = composerEnv.buildZipPackage {
        name = "theofidry-alice-data-fixtures-79913820cf6965cd6ea204cc5882079486f8262e";
        src = fetchurl {
          url = https://api.github.com/repos/theofidry/AliceDataFixtures/zipball/79913820cf6965cd6ea204cc5882079486f8262e;
          sha256 = "108862vlq2j10x69h5i4f4vqh63rfizvkc5kl57ymssk79ajifz2";
        };
      };
    };

Nix uses this expression for fetching wanted package and version in the given URL, and it fetches only once if version not changed in the composer.lock file.

So, as you have seen, there is no need for composer install. After the package installation process, we need the dumping autoload.php file. Nix makes that and runs composer install for checking everything is ok.

This roughly explains how composer2nix works and how to build your PHP project with nix.

But I faced a problem when I run nixops deploy command. Seems a package wants to modify a file in vendor/ocramius/package-versions/src/PackageVersions/Versions.php.

I said below; nix independently install packages in separate read-only folders. Ok. What can I do? I created an issue on the GitHub. Ocramius gently responded it and said it works if I disable running composer scripts with --no-scripts parameter. Ok, problem solved for now. But I have many scripts like assets:install or cache:clear

Now, I must manually clear caches all of the backends.

Someone should be saying, “Why did you use this package and not another package?” Sorry guys, it is not about me, Sylius depends on these packages.

I decided to write this blog. Forget the Nix. It is not only about nix deployment.

I want to ask some questions to PHP package developers.

Why you are building something after package install inside the package folder. Composer not for that.

It is not only about composer scripts. Why do you modify files inside your package folder? Why vendor/<package> is not stateless?

Please don't do that. Don't touch your package folder after install.

ps: if you curios about our conversation with @ocramius about this situation, you can read from github issue

#composer #nix #php #symfony #sylius #en #nixops #composer2nix

When you develop an API client with PHP, you would need data mapper for mapping data from API to an object.

For example, you have requested an API endpoint and you get a response like that:

{
  "user": {
    "username": "delirehberi",
    "bio": "carpenter"
  }
}

And you have a User class

<?php
class User{ 
    protected $username; 
    protected $fullname; 
    protected $bio;
      /**
     * @return mixed
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * @param mixed $username
     *
     * @return User
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getFullname()
    {
        return $this->fullname;
    }

    /**
     * @param mixed $fullname
     *
     * @return User
     */
    public function setFullname($fullname)
    {
        $this->fullname = $fullname;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getBio()
    {
        return $this->bio;
    }

    /**
     * @param mixed $bio
     *
     * @return User
     */
    public function setBio($bio)
    {
        $this->bio = $bio;

        return $this;
    }
}

You need to map JSON response to the PHP object. But how?

You will need to know how to use ReflectionClass, PropertyAccess, AnnotationReader.

How do you map class properties to API response fields and returns an object?

At first, you need to decode JSON string to PHP array or object.

$result = json_decode($response, true);

After that, create a Reflection class;

$reflectionObj = new \ReflectionClass(User::class);

Now you can access all properties

$properties = $reflectionObj->getProperties();

You don't need to know your User Class has which properties anymore.

Now, we need PropertyAccessor for manipulating our User object.

$propertyAccessor = PropertyAccess::createPropertyAccessor();

How can manipulate an object property with PropertyAccess object?

$propertyAccessor->setValue(
                    $userObject,
                    $property->getName(), //eg: username
                    $value //eg: emre
                );

OK, we know how to access the property of an object and how to manipulate another object properties.

Now we can start writing our data mapper.

Create an annotation class to configure properties to json values.

use Doctrine\Common\Annotations\Annotation;

/**
 * Class DataMapper.
 *
 * @Annotation
 * @Annotation\Target({"PROPERTY"})
 */
class DataMapper
{
    public $json_field;
}

We will use this object to define a JSON field equal to an object property. We can update our User class property annotations like that:

 /**
     * @var
     * @DataMapper(json_field="username")
     */
    protected $username;
    /**
     * @var
     * @DataMapper(json_field="full_name")
     */
    protected $fullname;
    /**
     * @var
     * @DataMapper(json_field="bio")
     */
    protected $bio;

note: don't forget to add DataMapper class to the header.

We are ready for writing data mapping logic.

  • Create an annotation reader object
  • Create property accessor
  • Create a new empty object (eg: User)
  • Create a reflection object for reach properties
  • Get object properties from reflection object
  • Foreach to properties
    • read property annotation for DataMapper annotation
    • get json_field value from annotation
    • access data from JSON with json_field value
    • set property of a new object with data
  • return new filled object :)
$reader = new AnnotationReader();
$propertyAccessor = PropertyAccess::createPropertyAccessor();
$user = new User();
$reflectionObj = new \ReflectionClass(User::class);
$properties = $reflectionObj->getProperties();
foreach ($properties as $key => $property) {
  $propertyAnnotation = $reader->getPropertyAnnotation($property, DataMapper::class);
  $json_field = $propertyAnnotation->json_field;
  if(!$json_field) continue;
  $value = $result[$json_field];
  $propertyAccessor->setValue(
                    $user,
                    $property->getName(),
                    $value
                );
}

var_dump($user);

Congrats, you can write as a function and use in your project.

#php #reflection #datamapper #annotation #annotationreader #doctrine #symfony

Sometimes you get an error like that:

container extension liip_imagine not registered

You double-checked all configurations and you are absolutely sure nothing is wrong.

But code not works, why?

Because Php can't convert i to I if os language is not en. Your code searches Liipİmagine and can't find that. Change os's lang code to en_US.UTF-8 for fix that.

Or add this line to .bashrc or .profile file.

export LANG=en_US.UTF-8

bb.

#errors #php #debug #en