Showing posts with label Composer. Show all posts
Showing posts with label Composer. Show all posts

Thursday, July 21, 2016

Using the Symfony Validator component as a standalone library

Sometimes I need to build some scripts without using a full framework like Symfony. The good news is that you can use individual components from Symfony. Most often I will be using the HTTPFoundation component, but for today I want to build a CLI script so no need for HTTPFoundation.
I  will be writing a small command line script to read and validate a  CSV file. For the  validation part I will be using the Symfony Validator Component,

Source of inspiration: https://blog.tinned-software.net/using-the-symfony-validator-as-a-standalone-component/


First step, install Symfony Validator using Composer:

                          composer require symfony/validator

and require the autoloader created by Composer.


<?php

require_once 'vendor/autoload.php';
require_once 'Loader.php';

use Symfony\Component\Validator\Validation;

$file = "someFile.csv";


$outputFile = "errorsLog" . "_" . time() . ".txt";
  
$validator = Validation::createValidatorBuilder()
 ->addMethodMapping('loadValidatorMetadata')
 ->getValidator()
;

$loader = new Loader($file, $outputFile, $validator);
$loader->load();

I will pass to my Loader class constructor the CSV file to be read, the file where I want to save the output and an instance of the Symfony validator.

The CSV file is read line by line, and from each line I will be creating a Row object. The validator will validate the Row object against the rules (constraints).
In the Row class I will add the method "loadValidatorMetada" mentioned when instantiating the Validator. I've added several validations for better examplification:


<?php

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;


class Row
{
    private $id;         
    private $name;   
    private $personalNumber;    
    private $CountryCode;  

    public function __construct($rowNumber, $data)
    {
        $this->id = $rowNumber;
        $this->name= $data[0];
        $this->personalNumber = $data[2];
        $this->countryCode = $data[1];
    }
 
 
    /**
     * This method is where you define your validation rules.
    */
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        //Name 
        $metadata->addPropertyConstraint('name', new Assert\NotBlank());
 
        //Personal number
        $metadata->addPropertyConstraint('personalNumber', new Assert\NotBlank());
        $metadata->addPropertyConstraint('personalNumber', new Assert\Type(array(
            'type'    => 'digit',
            'message' => 'The value {{ value }} is not a valid personal number.',
         )));
  
         //Country code
         $metadata->addPropertyConstraint('countryCode', new Assert\NotBlank());
         $metadata->addPropertyConstraint('countryCode', new Assert\Choice(array(
            'choices' => array('DE', 'AT', 'LU', 'ES', 'FR', 'BE', 'NO', 'SI', 'SE', 'IT', 'DK'),
            'message' => '{{ value }} is not a valid country code!',
          )));
  
     }
}


When validating a Row object against these constraints an array of Errors will be returned by the Validator. Below is the Loader class.



<?php

require_once 'Row.php';


class Loader
{
 private $fileName;
 private $validator;
 private $outputFile;
 
 public function __construct($fileName, $outputFile, $validator)
 {
  $this->fileName = $fileName;
  $this->outputFile = $outputFile;
  $this->validator = $validator;

 }
 
 public function load()
 {
  $file_handle = fopen($this->fileName, "r");
  $fileOutputHandle = fopen($this->outputFile, "w");
  
  /* 
   * Keep track of the current row in .csv file
   */
  $rowNumber=1;
  
  /* 
   * Read the file line by line
   */
  while (!feof($file_handle) ) {

   $line_of_text = fgetcsv($file_handle, 0);
   
   if ($rowNumber === 1) {
    
      /*
       * Ignore the first row from file as it contains headers
       */
     
   } else {
       $row = new Row($rowNumber, $line_of_text);    
       $this->validateRow($row, $fileOutputHandle); 
   }
   
   $rowNumber++;
  }
  fclose($file_handle);
  fclose($fileOutputHandle);
 }
 
 
 public function validateRow(Row $row, $fileOutputHandle)
 {
  $errors = $this->validator->validate($row);
  
  foreach ($errors as $error) {
   $errorMsg = "At row:" 
      . $error->getRoot()->getId() 
      . "- Property: " 
      . $error->getPropertyPath()
      . ' - Message: ' 
      . $error->getMessage()
      . "\n";
   
   fwrite($fileOutputHandle, $errorMsg);

  }
 }
}

Sunday, March 20, 2016

CPANAHierarchyBundle - Symfony and Neo4j

I've just uploaded to Github my last project: HierarchyBundle


This Symfony bundle manages a company hierarchy retrieving and stroring information to a Neo4j database. The hierarchy is based on groups: PHP group is part of Software Developement group, which is part of IT, etc. The users are members of these groups having different kind of roles (manager, employee etc).

I've got the idea for this bundle from the Neo4j documentation: http://neo4j.com/docs/stable/examples-user-roles-in-graphs.html . Basically it says that relational database are not really suited for representing hierarchical data :)

HierarchyBundle is using the Neo4jPHP library https://github.com/jadell/neo4jphp . On top of this I've created a class called GroupHierarchyManagement which contains specific functions for this scope, like retrieving the complete hierarchy above a user:



    /**
  * Return the hierarchy of of groups above one user node
  *
  * @param  Everyman\Neo4j\Node   node
  * @return row| null
  */
    public function getGroupHierarchyOfUser($node)
    {
        $id = $node->getId();

        $queryString =  ' MATCH n-[rel:'. $this->defRelTypeUserToGroup .']->group '.
                        ' WHERE id(n)='.$id.' '.
                        ' WITH group '.
                        ' MATCH p=group-[r:PART_OF*..]->d '.
                        ' WHERE ID(d)='.$this->rootGroupId.' '.
                        ' RETURN nodes(p) as nodes';

        $query = new Query($this->client, $queryString);
        $result = $query->getResultSet();

        if ($result->count() != 0) {
            return $result->current()->current();
        } else {
            return;
        }
    }


This class is configured as a service with the Symfony Dependency Injection Container. There are several parameters to be configured in order to work correctly:


#app/config/config.yml

cpana_hierarchy:
    group_hierarchy_manager_neo4j:
        neo4j_user:  'neo4j'
        neo4j_password:  'parola'
        def_rel_type_group_to_group: 'PART_OF'
        def_rel_type_user_to_group: 'MEMBER_OF'
        root_group_id: '69374'
        manager_role_property: 'manager'
        default_property_group:  'name'
        default_property_user: 'name'


Th first two are the user and password in order to login to Neo4j database. Next are the types (names) of the relations between Groups and Groups or between Users and Groups.
We can say that the team "PHP " is "PART_OF" departmenet "Software developement".
Also we can say employee Kenny is "MEMBER_OF" team "PHP".

You need to specify which is the root group node of your hierarchy by prodiving the Neo4j Id of that node in parameter "root_group_id".

The relation "MEMBER_OF" between Users and Groups has a property called "role", you can define which is the value of this property for users which are managers of a certain group: it can be a number, or explicitly  "manager", or "master" :) etc.

A Group or a User node can have many properties, like name, description for Groups, or age, salary for Users, from these we need to configure which to be displayed by default when we talk about that node, the name should be the most obvious value.

With this service called " group_hierarchy_manager_neo4j" I've built an application with a front side where regular users can browse the data and an Admin area to edit  the data.
Below some photos to get the idea:
Front side:

  

Admin side :


Friday, September 25, 2015

cpana/basicblogbundle now on GitHub and Packagist

https://github.com/cristianpana86/BasicBlogBundle
https://packagist.org/packages/cpana/basicblogbundle
I've worked previously with GitHub, the new thing was to have the package on Packagist.com so it can be installed easily via Composer.
After creating my repository on GitHub I went on Packagist and created an account.
Following the instructions found on this article: http://www.sitepoint.com/listing-packages-on-packagist-for-composer/ I've created my composer.json document:

{
    "name":        "cpana/basicblogbundle",
    "type":        "symfony-bundle",
    "description": "Symfony Basic Blog Bundle",
    "keywords":    ["blog","symfony"],
    "homepage":    "https://github.com/cristianpana86/BasicBlogBundle",
    "license":     "MIT",
    "authors": [
        {
            "name": "Cristian Pana",
            "email": "cristianpana86@yahoo.com"
        }
   ],
   "require": {
       "php": ">=5.5.0",
       "symfony/symfony": "2.7.*",
        "doctrine/orm": "~2.2,>=2.2.3,<2.5",
        "doctrine/dbal": "<2.5",
        "doctrine/doctrine-bundle": "~1.4",
        "symfony/assetic-bundle": "~2.3",
        "symfony/swiftmailer-bundle": "~2.3",
        "symfony/monolog-bundle": "~2.4",
        "sensio/distribution-bundle": "~4.0",
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2"
    },
    "minimum-stability": "dev",
    "autoload" : {
        "psr-4" : {
            "CPANA\\BasicBlogBundle\\" : ""
        }
    }
}

Some of the values are from the Symblog composer.json and may be outdated, it's something I have to look over.
I've added also the installation instructions on my GitHub repository and from there are automatically listed on Packagist.com:

Install using Composer:
    composer require cpana/basicblogbundle:dev-master
 
Register the bundle in AppKernel.php by adding:
    new CPANA\BasicBlogBundle\CPANABasicBlogBundle(),
 
Import paths in app/config/routing.yml by adding:
    CPANABasicBlogBundle:
    resource: "@CPANABasicBlogBundle/Resources/config/routing.yml"
 
Make sure to have configured your database in app/config/parameters.yml Generate you schema using console:
    php app/console cache:clear
    php app/console doctrine:schema:update --force