Auditlogging functionality for zend db - zend-framework

I have a requirement to implement audit logging functionality in a zend project. The models are created using zend db and the update function is as follows.
public function updateGroup($data,$id)
{
$row = $this->find($id)->current();
// set the row data
$row->name = $data['name'];
$row->description = $data['description'];
$row->updatedBy = $data['updatedBy'];
$row->updatedOn = date('Y-m-d H:i:s');
$id = $row->save();
return $id;
}
I have to create a table with the auditlog information which includes the current userid. I have tried many methods and nothing is a good solution. What is the best practice for a good audit logging functionality for zend?
I just want to log only the modified data. and the log table schema is like
id,
table,
column,
rowId
oldvalue,
newvalue,
updatedon,
updatedbyuser

use Zend_Log_Writer_Db :
Zend_Log_Writer_Db writes log information to a database table using
Zend_Db. The constructor of Zend_Log_Writer_Db receives a
Zend_Db_Adapter instance, a table name, and a mapping of database
columns to event data items
for example :
$columnMapping = array('name' => 'name',
'desc' => 'desc',
'updatedBy' => 'userid',
'updatedOn' => 'date');
$writer = new Zend_Log_Writer_Db($db, 'auditlog_table', $columnMapping);
$logger = new Zend_Log($writer);
$logger->setEventItem('name', $data['name']);
$logger->setEventItem('desc', $data['name']);
$logger->setEventItem('updatedBy',$data['updatedBy']);
$logger->setEventItem('updatedOn',date('Y-m-d H:i:s'));
EDIT : to log only the modified data :
public function logUpdate(array $values)
{
$columnMapping = array('id' => 'id',
'table' => 'table',
'column' => 'column',
'rowId' => 'rowId',
'oldvalue' => 'oldvalue',
'newvalue' => 'newvalue',
'updatedon' => 'updatedon',
'updatedbyuser' => 'updatedbyuser');
$writer = new Zend_Log_Writer_Db($db, 'auditlog_table', $columnMapping);
$logger = new Zend_Log($writer);
$logger->setEventItem('id', $values['id']);
$logger->setEventItem('table', $values['table']);
$logger->setEventItem('column', $values['column']);
$logger->setEventItem('rowId', $values['rowId']);
$logger->setEventItem('oldvalue', $values['oldValue']);
$logger->setEventItem('newValue', $values['newValue']);
$logger->setEventItem('updatedon', $values['updatedon']);
$logger->setEventItem('updatedbyuser', $values['updatedbyuser']);
}
and in updateGroup :
public function updateGroup($data,$id)
{
$row = $this->find($id)->current();
$values = array('table' => $this->name);
$values = array('updatedon' => $data['updatedBy']);
$values = array('updatedbyuser' => date('Y-m-d H:i:s'));
//go through all data to log the modified columns
foreach($data as $key => $value){
//check if modified log the modification
if($row->$key != $value){
$values = array('column' => $key);
$values = array('oldValue' => $row->$key);
$values = array('newValue' => $value);
logUpdate($values);
}
}
// set the row data
$row->name = $data['name'];
$row->description = $data['description'];
$row->updatedBy = $data['updatedBy'];
$row->updatedOn = date('Y-m-d H:i:s');
$id = $row->save();
return $id;
}
Note that its better to implement logging for all your application and seperate logging from update , see this answer for that .

Related

PHP- PDO get metadata from database

I want to get the metadata from a database with a table 'friends'
id name
1 Herbert
2 LG
3 Levins
Here is the code I was trying to get the data.
<?php
$dsn = 'mysql:host=localhost;dbname=postgre';
$username = 'root';
$password = '';
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
);
$db = new PDO($dsn, $username, $password, $options);
$stmt = $db->query("SELECT * FROM friends");
$cnt_columns = $stmt->columnCount();
for($i = 0; $i < $cnt_columns; $i++) {
$metadata = $stmt->getColumnMeta($i);
var_dump($metadata);
}
?>
When I execute the code:, it displays
array
'native_type' => string 'LONG' (length=4)
'pdo_type' => int 2
'flags' =>
array
empty
'table' => string 'friends' (length=7)
'name' => string 'id' (length=2)
'len' => int 11
'precision' => int 0
array
'native_type' => string 'VAR_STRING' (length=10)
'pdo_type' => int 2
'flags' =>
array
empty
'table' => string 'friends' (length=7)
'name' => string 'name' (length=4)
'len' => int 60
'precision' => int 0
Till here, it is giving the correct count of rows, but I need the result to display as it looks in the my database like
Output:
id name
1 Hebert
2 LG
3 Levins
How could I get all my fields as it is like a table in my database using metadata.
Use columnCount.
$stmt = $db->query("SELECT * FROM friends");
$cnt_columns = $stmt->columnCount();
for($i = 0; $i < $cnt_columns; $i++) {
$metadata = $stmt->getColumnMeta($i);
var_dump($metadata);
}
By the way, getColumnMeta is experimental. Not recommended to use. Why do you want to use it?
For the desired output, you don't need metadata. Just loop through the results:
$sql = "SELECT * FROM friends";
$stmt = $db->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll();
// field names
if(count($result) > 0) {
foreach($result[0] as $k => $v) {
if(!is_int($k)) {
echo $k . "\t";
}
}
}
echo PHP_EOL;
// data
foreach ($result as $row) {
foreach($row as $k => $v) {
if(!is_int($k)) {
echo $row[$k] . "\t";
}
}
echo PHP_EOL;
}

Form bindAndSave method doesn't save files

Parsing an XML feed and save it this way:
$action = new CouponActionForm();
$values = array(
'name' => $offer->name,
'link' => $offer->url,
'description' => $offer->description,
'discount' => $offer->discount,
'price' => $offer->price
);
$files = $this->getImages($offer->picture);
if(!$action->bindAndSave($values, $files)){
echo $action->renderGlobalErrors();
die('BAD THING');
}
Here is the getImages method:
private function getImages($url){
$ret = array('error' => 0);
$tmp_dir = sys_get_temp_dir();
$tmp_file = tempnam($tmp_dir, 'kupon');
if(copy($url, $tmp_file)){
$ret['tmp_name'] = $tmp_file;
$ret['name'] = basename($url);
$ret['size'] = filesize($tmp_file);
$ret['type'] = 'image/jpeg';
return array('filename' => $ret);
}else{
die('HELP');
}
}
Why do I use form saving instead of object saving with ->fromArray method ? Well, I already made the form and all validation, so I don't want to implement the same thing twice (DRY), but as important, I actually don't know how to use validators in doctrine model.
So the problem is that the form doesn't save files in specific directory specified by the path property of sfValidatorFile validator, but it exists in tmp directory.

Zend form populate method

I have a Zend form below is the code
public function editAction()
{
try
{
$data = Zend_Auth::getInstance()->getStorage()->read();
$this->view->UserInfo = $data['UserInfo'];
$this->view->Account = $data['Account'];
$UserEditForm = $this->getUserEditForm();
$this->view->UserEditForm = $UserEditForm;
$params = $this->_request->getParams();
if ($params['user'])
{
$UserResult = $this->_user_model->getUserData($params['user']);
$UserAddressResult = $this->_user_model->getAddressData($params['user']);
$UserInfo = Array(
'UserId' => $UserResult['user_id'],
'EmailAddress' => $UserResult['email'],
'UserName' => $UserResult['username'],
'Title' => $UserResult['Title'],
'FirstName' => $UserResult['firstname'],
'LastName' => $UserResult['lastname'],
'Gender' => $UserResult['gender'],
'DateOfBirth' => date('m/d/Y', strtotime($UserResult['dateofbirth'])),
'AddressLine1' => $UserAddressResult['address_1'],
'AddressLine2' => $UserAddressResult['address_2'],
'City' => $UserAddressResult['city'],
'State' => $UserAddressResult['state_id'],
'PostalCode' => $UserAddressResult['postcode'],
'Country' => $UserAddressResult['country_id'],
'CompanyName' => $UserAddressResult['company'],
'WorkPhone' => $UserAddressResult['workphone'],
'HomePhone' => $UserAddressResult['homephone'],
'Fax' => $UserAddressResult['fax'],
'IsDashboardUser' => $UserResult['is_dashboard_user']
);
$UserEditForm->populate($UserInfo);
$this->view->UserEditForm = $UserEditForm;
}
if ($this->_request->isPost())
{
$values = $this->_request->getPost();
unset($values['Save']);
if ($UserEditForm->isValid($values))
{
$Modified_date = date('Y-m-d H:i:s');
$UserData = $this->_user_model->CheckEmail($values['EmailAddress']);
$UpdateData = $this->_user_model->UpdateUserData($UserData['user_id'], $values, $Modified_date, $data['UserId']);
if ($UpdateData != null)
{
return $this->_helper->redirector('userlist','index','user');
}
}
}
}
catch (Exception $exception)
{
echo $exception->getCode();
echo $exception->getMessage();
}
}
Because my form elements names are different then my table field names i have to declare a Array $UserAddressResult see above code to match the table field name with form element name.
Is there a another way to populate form without declaring this array.
Please don't suggest that i have to keep my table field name and form element name same. I cannot do that as per our naming convention standards.
Your naming convention is inconsistent. If it were consistent, you could perhaps use a simple regular expression to transform column names. But you can't, so you will have to do it manually one way or the other.
If you need to construct this array of values in more than one place in your code, you should consider moving the logic to the form itself (extend it with a public function populateFromUserAndResult($userResult, $userAddress)) or an action helper.
We decided to keep same name of form elements as per our table fields name. For now this is the only solutions i feel. if i come across any other solutions in future will update here...
Thanks for all your replies....

Zend_Db_Table_Abstract Loading Joined Models

I have a tables named:
client (id, alias)
post (id, subject)
post_client (id, post_id, client_id)
Many clients can be joined to a post.
Using Zend DB Table abstract I have started to build a model, here are the classes:
ORM_Post
class ORM_Post extends Zend_Db_Table_Abstract {
protected $_name = 'Post';
protected $_dependentTables = array('ORM_Post_Client');
}
ORM_Client
class ORM_Client extends Zend_Db_Table_Abstract {
protected $_name = 'Client';
protected $_dependentTables = array(
'ORM_Post_Client'
);
}
ORM_Post_Client
class ORM_Post_Client extends Zend_Db_Table_Abstract {
protected $_name = 'Post_Client';
protected $_referenceMap = array(
'post' => array(
'columns' => 'post_id',
'refTableClass' => 'ORM_Post',
'refColumns' => 'id'
),
'client' => array(
'columns' => 'client_id',
'refTableClass' => 'ORM_Post_Client',
'refColumns' => 'id'
)
);
}
What I was hoping todo is call an instance of the Post and then load the clients associated aswell as loading an instance of the client and load all posts associated.
So I did this:
$post = new ORM_Post();
$results = $post->fetchAll();
foreach ($results as $key => $result){
$row = $results->current();
$client = $row->findDependentRowset('ORM_Post_Client','client');
}
and I get
Reference rule "client" does not reference table ORM_Post
I have battled with this for hours and cannot see where I'm going wrong. Am I to declare the Post_Client joins inside the client and post model also?
EDIT
Here is what I was after:
$post = new ORM_Post();
$results = $post->fetchAll();
$return = array();
foreach ($results as $result){
$row = $post->find($result->id)->current();
$return[$result->id] = $row->toArray();
$return[$result->id]['clients'] = $row->findManyToManyRowset('ORM_Client', 'ORM_Post_Client')->toArray();
}
return $return;
Thanks for the advice guys, you put me on the right track
in your ORM_Post_Client it should be
'client' => array(
'columns' => 'client_id',
'refTableClass' => 'ORM_Client', //instead of ORM_Post_Client
'refColumns' => 'id'
)
refTableClass => The class name of the parent table. Use the class
name, not the physical name of the SQL table (documentation)
also i think your loop should be :
foreach ($results as $result){
$row = $results->current();
$clients = $row->findDependentRowset('ORM_Post_Client','post');
}
because you are looking for clients of a post which means that post is your rule
($row->findDependentRowset($table, [$rule]); )
This as presented won't work, honestly it makes no sense.
$post = new ORM_Post();
$results = $post->fetchAll();
foreach ($results as $key => $result){
//$row is assigned to the whole fetchall result!
$row = $results->current();
//in this context $client cannot call a dependent rowset.
$client = $row->findDependentRowset('ORM_Post_Client','client');
}
MMc is correct in that you reference table definition was incorrect however your code has some issues as well. Maybe try something like:
$post = new ORM_Post();
$results = $post->fetchAll();
//unless your are going to use the 'key' for something you don't need it
foreach ($results as $result){
//you need each row object in order to call findDependentRowset in a one to many relationship.
$row = $post->find($result->id)->current();
//unless you have multiple rules set up for each table class pair you don't need to specify the rule.
$client = $row->findDependentRowset('ORM_Post_Client');
}

How do I populate a Zend Form from a Doctrine Model with Many To One relationships?

I have an entity setup called Lead which contains a car make, model, etc and this Lead is mapped by a Many to One relationship to a Client entity, which has forname, surname, etc.
i.e. A client may have many leads
I have created a toArray function which gets the data from the Lead,
public function toArray()
{
$record_data = get_object_vars($this);
$formatted_record_data = array();
foreach($record_data as $name=>$value){
if (is_object($value)){
if (get_class($value) == 'DateTime') {
$value = $this->datetimeToString($value);
} else {
$value = $value->toArray();
}
}
$formatted_record_data[$this->from_camel_case($name)] = $value;
}
return $formatted_record_data;
}
which then populates a Zend Form using:
$record = $this->_em->getRepository($this->_entity)->find($this->_primaryId);
$form->setDefaults($record->toArray());
This works fine for the fields for the Lead entity which are populated, but it does not populate the Client-based fields e.g. forname.
EDIT
I have fixed my problem by the following method:
1) Adding the following method to my Update Action.
$this->_record = $this->_em->getRepository($this->_entity)->find($this->_primaryId);
$this->_form->setRecord($this->_record);
$this->view->form = $this->_form;
2) Adding the following method to my Form Model
public function setRecord($record)
{
$data = array('registration' => $record->registration,
'make' => $record->make,
'model' => $record->model,
'pav' => $record->pav,
'salvage_value' => $record->salvageValue,
'forname' => $record->client->forname,
'surname' => $record->client->surname,
'vehicle_address1' => $record->vehicleAddress1,
'vehicle_address2' => $record->vehicleAddress2,
'vehicle_address3' => $record->vehicleAddress3,
'vehicle_address4' => $record->vehicleAddress4,
'vehicle_address5' => $record->vehicleAddress5,
'vehicle_postcode' => $record->vehiclePostcode,
'category' => $record->category,
'colour' => $record->colour
);
$this->setDefaults($data);
}
This way I can manually get the related data, in this case:
'forname' => $record->client->forname,
'surname' => $record->client->surname,
and add them to the form using:
$this->setDefaults($data);
try :
$leads = $record->toArray();
$client_info = array('name' => 'test', 'surname' => 'test 2'); // or if from db use what you did for the leads.
$defaults = array_merge($leads, $client_info);
$form->setDefaults($defaults);