Zf2 Doctrine 2 Log Every Entity Change

September 18, 2015

In this article I will show a simple way to log every change to doctrine 2 entity.
This one will not be copy / paste. Just check out the code and help yourself.

1. Create event service

/**
 * @developer Ivan Gospodinow
 */

namespace Application\Service;

use Application\Entity\DbLogEntity;
use Application\Entity\AbstractEntity;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use \DateTime;

class DoctrineLogChangesService
{

    /**
     * Hold the changes
     * @var type
     */
    protected static $rows = [];
    protected $sm;
    protected $disabled = [
        //'Application\Entity\UserEntity',
    ];

    public function __construct($sm)
    {
        $this->sm = $sm;
        $sm->get(‘Application’)
                ->getEventManager()
                ->attach(‘finish ’, [$this, 'flush']);
    }

    public function flush()
    {
        $orm = $this->sm->get(‘doctrine.entitymanager.orm_default’);

        foreach (self::$rows as $row) {
            $orm->persist($row);
        }
        if (!empty(self::$rows)) {
            $orm->flush();
        }
    }

    public function getUnitOfWork()
    {
        return $this->sm->get(‘doctrine.entitymanager.orm_default’)->getUnitOfWork();
    }

    public function getEntityTableName(LifecycleEventArgs $args)
    {
        $meta = $args->getObjectManager()->getClassMetadata(get_class($args->getObject()));
        return $meta->table['name'];
    }

    public function getEntityFields(LifecycleEventArgs $args)
    {
        $meta = $args->getObjectManager()->getClassMetadata(get_class($args->getObject()));
        $fields = [];
        foreach ($meta->fieldMappings as $field) {
            $fields[$field['fieldName']] = $field['columnName'];
        }

        foreach ($meta->associationMappings as $field) {
            if (isset($field['sourceToTargetKeyColumns'])) {
                $fields[$field['fieldName']] = key($field['sourceToTargetKeyColumns']);
            }
        }

        return $fields;
    }

    /**
     * Adds all initial fields
     * @param LifecycleEventArgs $args
     * @return type
     */
    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        if (in_array(get_class($entity), $this->disabled)) {
            return;
        }

        $tableName = $this->getEntityTableName($args);
        $fields = $this->getEntityFields($args);

        foreach ($fields as $field => $column) {
            $value = call_user_func_array([$entity, 'get' . ucfirst($field)], []);
            $row = [
                'user' => $this->sm->get('User')->getId(),
                'table' => $tableName,
                'pk' => $entity->getId(),
                'column' => $column,
                'oldValue' => null,
                'newValue' => $value instanceof AbstractEntity ? $value->getId() : $value
            ];
            $log = new DbLogEntity();
            $log->exchangeArray($row);
            self::$rows[] = $log;
        }
    }

    /**
     * Add only new changes
     * @param LifecycleEventArgs $args
     * @return type
     */
    public function preUpdate(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        if (in_array(get_class($entity), $this->disabled) || !$entity->canUseLifecycleEvents()) {
            return;
        }

        $tableName = $this->getEntityTableName($args);
        $fields = $this->getEntityFields($args);
        $set = $this->getUnitOfWork()->getEntityChangeSet($entity);
        foreach ($set as $property => $changes) {
            $oldValue = $changes[0];
            $newValue = $changes[1];
            if ($changes[0] instanceof AbstractEntity || $changes[1] instanceof AbstractEntity) {
                if ($changes[0] instanceof AbstractEntity) {
                    $oldValue = $changes[0]->getId();
                }
                if ($changes[1] instanceof AbstractEntity) {
                    $newValue = $changes[1]->getId();
                }
            }

            if ($oldValue == $newValue) {
                continue;
            }

            $row = [
                ‘user’ => $this->sm->get(‘User’)->getId(),
                ‘ table’ => $tableName,
                ‘ pk’ => $entity->getId(),
                ‘ column’ => $fields[$property],
                ‘ oldValue’ => $this->getStringValue($oldValue),
                ‘ newValue’ => $this->getStringValue($newValue)
            ];

            $log = new DbLogEntity();
            $log->exchangeArray($row);
            self::$rows[] = $log;
        }
    }

    public function getStringValue($value)
    {
        if (is_scalar($value)) {
            return $value;
        }

        if ($value instanceof DateTime) {
            return $value->format('Y-m-d H:i:s');
        } elseif (is_array($value)) {
            return serialize($value);
        }

        return $value;
    }

}

2. Add the event service to Doctrine 2

/**
 * @developer Ivan Gospodinow
 */

namespace Application;

use Zend\Mvc\MvcEvent;

class Module
{

    public function onBootstrap(MvcEvent $e)
    {
        $application = $e->getApplication();
        $sm = $application->getServiceManager();
        $doctrineEntityManager = $sm->get('doctrine.entitymanager.orm_default');
        $doctrineEventManager = $doctrineEntityManager->getEventManager();
        $doctrineEventManager->addEventListener(
            [
                \Doctrine\ORM\Events::postPersist,
                \Doctrine\ORM\Events::preUpdate,
            ],
            new Service\DoctrineLogChangesService($sm)
        );
    }
}

3. DbLogEntity

/**
 * @developer Ivan Gospodinow
 */

namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;
use \Exception;

/**
 * @ORM\Table(name="__dblog")
 * @ORM\Entity()
 */
class DbLogEntity
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer", name="id")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", name="user_id")
     */
    protected $user;

    /**
     * @ORM\Column(type="string", name="table_name")
     */
    protected $table;

    /**
     * @ORM\Column(type="integer", name="pk")
     */
    protected $pk;

    /**
     * @ORM\Column(type="string", name="column_name")
     */
    protected $column;

    /**
     * @ORM\Column(type="string", name="old_value")
     */
    protected $oldValue;

    /**
     * @ORM\Column(type="string", name="new_value")
     */
    protected $newValue;

    /**
     * @ORM\Column(type="datetime", name="timestamp")
     */
    protected $timestamp;

}

Simple as that.
Suggestions or problems ? Write a comment.

Zend Framework 2 change layout for ajax requests

September 15, 2015

How to change layout for ajax requests in Zend Framework 2

1. Module.php

    public function onBootstrap(MvcEvent $e)
    {
        $e->getApplication()
          ->getEventManager()
          ->getSharedManager()
          ->attach(
              'Zend\Mvc\Controller\AbstractController',
              'dispatch',
              function ($e) {
                  $controller      = $e->getTarget();
                  if ($controller->getRequest() instanceof HttpRequest) {
                    if ($controller->getRequest()->isXmlHttpRequest()) {
                          $controller->layout('layout/ajax');
                    }
                  }
              },
              100
          );
    }

2. module.config.php Add the layout path

    'view_manager' => array(
        'template_map' => array(
            'layout/ajax' => __DIR__ . '/../view/layout/layout.ajax.phtml',
        ),
    ),

Creating issues in Jira for multiple users with Jira API

September 12, 2015

1. Creating Jira issue

   $jiraUsername = 'Jira login username';
   $jiraPassword = 'Jira login password';
   $jiraHost     = 'http://my-jira-host.com/';
   $data = array(
        'fields' => array(
            'project' => array('key' => 'PR'/* Project Key*/),
            'summary'=> 'Issue title',
            'description' => 'Some text here, no html tags',
            'labels' => array('API', 'IvanGospodinow'/* Put the username so you know which one added it */),
            'issuetype' => array('id' => 1 /* or similar */)
        )
    );

    $ch = curl_init();
    $headers = array(
        'Accept: application/json',
        'Content-Type: application/json'
    );
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_VERBOSE, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_URL, $jiraHost . 'rest/api/2/issue/');
    curl_setopt($ch, CURLOPT_USERPWD, "$jiraUsername:$jiraPassword");
    $response = curl_exec($ch);
    $error = curl_error($ch);
    if (!empty($error)) {
        throw new Exception('EQ Error: ' . $error);
    }
    curl_close($ch)

2. Getting all issues for user

        $ch = curl_init();
        $headers = array(
            'Accept: application/json',
            'Content-Type: application/json'
        );
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_VERBOSE, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_URL, $jiraHost . 'rest/api/2/search?jql=' . urlencode('labels in (API, 'IvanGospodinow')'));
        curl_setopt($ch, CURLOPT_USERPWD, "$jiraUsername:$jiraPassword");
        $response = curl_exec($ch);
        curl_close($ch);

Simple as that.

Zend Framework 2 load multiple config files from Module

September 7, 2015

Module.php

    public function getConfig()
    {
        return
        (include __DIR__ . '/config/module.config.php')
        +
        (include __DIR__ . '/config/module.config.permissions.php');
    }

Simple as that.

Copy big MySQL database with php in 5 steps

July 17, 2015

1. Dump structure of the big database.
2. Open the sql file and cut the constraints.
3.  Import the structure to another database.
4. Run this code:

/**
 * Sync from live database
 */
ini_set('memory_limit','512M');

$limit      = 100000;
$time       = time();
$sleep      = 1;
$sleepEvery = 60;

$link = new mysqli(
    "__HOST__",
    "__USER__",
    "__PASSWORD__",
    "__DATABASE__"
);
$link->set_charset("utf8");
  
$importLink = new mysqli(
    "__HOST__",
    "__USER__",
    "__PASSWORD__",
    "__DATABASE__"
);
$importLink->set_charset("utf8");
$result = $link->query('SHOW TABLES');


$tables = [];
while($row = mysqli_fetch_assoc($result)) {
    /**
     * Filter the tables if you want
     */
    $table = $row[key($row)];
    $tables[] = $table;
}

foreach ($tables as $table)
{
    $offset = 0;
    $hasRows = true;
    while ($hasRows) {
        echo "Excecuting SELECT * FROM `$table` LIMIT $limit OFFSET $offset" . PHP_EOL;
        flush();
        $result = $link->query("SELECT * FROM `$table` LIMIT $limit OFFSET $offset");
        if (!$result) {
            $hasRows = false;
            break;
        }
        
        $rows = [];
        while($row = mysqli_fetch_assoc($result)) {
           $rows[] = $row;
        }
        
        if (!empty($rows)) {
            dumpToDatabase($table, $rows);
        }
        
        $offset += $limit;
        takeANap();
        if (count($rows) < $limit) {
            $hasRows = false;
            break;
        }
    }
}


function takeANap()
{
    global $time, $sleep, $sleepEvery;
    if ($time + $sleepEvery <= time()) {
        $time = time();
        echo 'Sleep for ' . $sleep . PHP_EOL;
        flush();
        sleep($sleep);
    }
    
}

function dumpToDatabase($table, $rows)
{
    global $importLink;
    echo 'begin_transaction' . PHP_EOL;
    $importLink->begin_transaction();
    $inserts = [];
    foreach  ($rows as $row) {
        $values = [];
        foreach ($row as $id => $value) {
            $values[] = "'".mysqli_real_escape_string($importLink, $value)."'";
        }
        
        $inserts[] = sprintf(
            '(%s)',
            implode(',', $values)
        );
        
        if (count($inserts) >= 1000) {
            $str = sprintf(
                'INSERT INTO `%s` (%s) VALUES %s;',
                $table,
                implode(',', array_keys($row)),
                implode(',', $inserts)
            );

            $importLink->query($str);
            echo mysqli_error($importLink) . PHP_EOL;
            $inserts = [];
        }
    }
    
    if (!empty($inserts)) {
        $str = sprintf(
            'INSERT INTO `%s` (%s) VALUES %s;',
            $table,
            implode(',', array_keys($row)),
            implode(',', $inserts)
        );

        $importLink->query($str);
    }
    echo 'commit' . PHP_EOL;
    $importLink->commit();
}


die('END');

5. Add the constrains from step 2.

Easy as that.

Warning: Division by zero – easy way to get arround?

September 26, 2014
$sum = [0, 0, 0, 0];

echo count($sum) / array_sum($sum);
// Warning: Division by zero

$arraySum = 0.00001 + array_sum($sum);
$number = count($sum) / $arraySum;
echo round($number, 2);
// Result is 0;

Just add some small number to the one you are not sure that it will be bigger than zero.
Easy as that.

Zend Framework 2 Cache Proxy – Cache Based Key Dependency.

July 5, 2014

Long time no ZF2 post, but here is something interesting peace of code that can solve cache problems present in most of the projects.

I am assuming that, the reader knows how to create custom cache Factory in ZF2.

Step 1.

Edit your existing cache factory.

See Line 35.

namespace Application\Factory;

use Application\Factory\CacheProxy; // Or whatever location
use Zend\Cache\Storage\Plugin\Serializer;
use Zend\Cache\StorageFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class CacheFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $sm)
    {
        $config = $sm->get('config');

        $cache = StorageFactory::factory(array(
                    'adapter' => array(
                        'name' => 'filesystem',
                    ),
                    'plugins' => array(
                        // Don't throw exceptions on cache errors
                        'exception_handler' => array(
                            'throw_exceptions' => true
                        ),
                    )
        ));

        if (!file_exists($config['cache']['cache_dir'])) {
            mkdir($config['cache']['cache_dir'], 0755, true);
        }

        $cache->getOptions()->setTtl((int) $config['cache']['cache_ttl']);
        $cache->getOptions()->setCacheDir($config['cache']['cache_dir']);
        $cache->addPlugin(new Serializer);

        return new CacheProxy($cache);
    }
}

Step 2.

CacheProxy Code

namespace Application\Factory;

use Zend\Cache\Storage\StorageInterface;

class CacheProxy
{
    /**
     *
     * @var \Zend\Cache\Storage\StorageInterface
     */
    protected $cache;

    /**
     * We are using the fact that $key is the first parameter
     * and pass by reference is not present.
     * @var type 
     */
    protected $override = [
        'hasItem',
        'getMetadata',
        'setItem',
        'addItem',
        'replaceItem',
        'touchItem',
        'removeItem',
        'incrementItem',
        'decrementItem'
    ];

    public function __construct(StorageInterface $cache)
    {
        $this->cache = $cache;
    }

    /**
     * 
     * @param type $name
     * @param type $arguments
     * @return type
     */
    public function __call($name, $arguments)
    {
        if (in_array($name, $this->override)) {
            $arguments[0] = $this->getKey($arguments[0]);
        }
        $return = call_user_func_array([$this->cache, $name], $arguments);
        /**
         * Ensure that chain calls will work as expected.
         */
        if ($return instanceof StorageInterface) {
            return new self($return);
        } else {
            return $return;
        }
    }

    /**
     * 
     * @param type $key
     * @param type $success
     * @param type $casToken
     */
    public function getItem($key, &$success = null, &$casToken = null)
    {
        return $this->cache->getItem($this->getKey($key), $success, $casToken);
    }

    /**
     * @param type $token
     * @param type $key
     * @param type $value
     * @return type
     */
    public function checkAndSetItem($token, $key, $value)
    {
        return $this->cache->replaceItem($token, $this->getKey($key), $value);
    }

    /**
     * 
     * @param type $key
     * @return type
     */
    protected function getKey($key)
    {
        return sha1(
            sprintf(
                '%s-%s-%s',
                $key,
                defined('APPLICATION_ENV')     ? APPLICATION_ENV : '',
                defined('APPLICATION_VERSION') ? APPLICATION_VERSION : ''
            )
        );
    }
}

Simple as that.

Suggestions or problems ? Write a comment.

How to create google login on my site with openid in 5 min

August 21, 2013

As always we go straight to coding.

Download how-to-create-google-login-on-my-site-with-openid-in-5-min.zip

mode) {
		// the google openid url
		$openid->identity = 'https://www.google.com/accounts/o8/id';

		// information for for the user
		$openid->required = array('contact/email', 'namePerson/first', 'namePerson/last');

		// redirect to google
		header('Location: ' . $openid -> authUrl());
	} elseif ($openid->mode == 'cancel') {
		// user cancel the login
		// redirect may be ?
	} else {
		if ($openid->validate()) {
			// user logged in
			$d = $openid->getAttributes();

			$first_name = $d['namePerson/first'];
			$last_name = $d['namePerson/last'];
			$email = $d['contact/email'];

			// do your login

		} else {
			//user is not logged in
		}
	}
} catch(Exception $e) {
	// something went wrong
	echo $e->getMessage();
	exit;
}

Simple as that.

Suggestions or problems ? Write a comment.

How to get an unique identifier for php closure ?

June 17, 2013

Right to the example…

$identifier = spl_object_hash(function(){
  // some function content
});

Simple as that.

Suggestions or problems ? Write a comment.

How to read csv file into array ?

June 16, 2013
function csvToArray($file){
	$rows = array();
	$headers = array();
	if(file_exists($file) && is_readable($file)){
		$handle = fopen($file, 'r');
		while (!feof($handle) ) {
			$row = fgetcsv($handle, 10240);
			if(empty($headers))
				$headers = $row;
			else if(is_array($row))
				$rows[] = array_combine($headers, $row);
		}
		fclose($handle);
	} else {
		throw new Exception($file.' doesn`t exist or is not readable.');
	}
	return $rows;
}

Simple as that.

Suggestions or problems ? Write a comment.


Warning: Use of undefined constant XML - assumed 'XML' (this will throw an Error in a future version of PHP) in /home/c2kblate/sites/ivangospodinow.com/wp-content/plugins/wp-syntaxhighlighter/wp-syntaxhighlighter.php on line 1048
 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org