Laravel Eloquent - Encrypting/Decrypt Data on call - encryption

I can use Crypt to encrypt/decrypt my data. I want to encrypt some information in my db (such as the name, email, phone number to name a few).
Assuming that I want EVERYTHING to be encrypted, I want to be able to do this in the background by itself, which I can perform by overwriting the create and save functions:
// For instance, the save() function could become
public function save(array $options = array())
{
foreach ($this->attributes as $key => $value)
{
if (isset($value)) $this->attributes[$key] = Crypt::encrypt($value);
}
return parent::save($options);
}
Now, I want the decryption to be performed the same way, so that when I say User::find($id), the returned $user is already decrypted. Also other functions such as firstOrFail() get() first() and all to work as well.
I also would like this functionality to be extended when I use relationships (so User::with('someOtherTable')->find($id) also work).
Would this be possible? If this is not possible, I am thinking of creating a helper function decyrpt()
function decrypt($array)
{
if (!is_array($array)) return Crypt::decrypt($array);
$result = [];
foreach($array as $key => $value) $result[$key] = decrypt($value);
return $result;
}
And pass all my results through this first, and then start using them, but it would be nicer if Laravel would provide this, or if there was a "Laravel Way" of doing this.

It doesn't really make sense to encrypt everything. For example, you never want to encrypt the primary key; that doesn't even make sense. Likewise you probably don't want to encrypt the date fields; you'll lose the ability to perform any sort of SQL query on them.
With that in mind, you can try something like this:
class BaseModel extends Eloquent {
protected $encrypt = [];
public function setAttribute($key, $value)
{
if (in_array($key, $this->encrypt))
{
$value = Crypt::encrypt($value);
}
return parent::setAttribute($key, $value);
}
public function getAttribute($key)
{
if (in_array($key, $this->encrypt))
{
return Crypt::decrypt($this->attributes[$key]);
}
return parent::getAttribute($key);
}
public function attributesToArray()
{
$attributes = parent::attributesToArray();
foreach ($attributes as $key => $value)
{
if (in_array($key, $this->encrypt))
{
$attributes[$key] = Crypt::decrypt($value);
}
}
return $attributes;
}
}
then have all you models extend this one, and set the $encrypt property to whatever columns you want encrypted for that particular model.
P.S. If you want to use Eloquent's accessor functionality, you'll have to play with this a bit more.

It's worth mentioning Elocrypt library for Laravel 4. It's a more elaborate solution that works the same way. If you're using Laravel 5 use this one instead: Elocrypt 5.

Related

Array as parameter in Doctrine SQLFilter

I'm trying to create Doctrine SQLFilter. I need to filter for "deleted" field. But i want to make filter works with both (true and false) values too.
Something like this:
<?php
namespace Rem\CostsBundle\Doctrine;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class ItemDeletedFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->getReflectionClass()->name != 'Rem\CostsBundle\Entity\Item') {
return '';
}
$fdata = $this->getParameter('deleted');
$filter = '1<>1';
foreach ($fdata as $param) {
$filter .= sprintf('OR %s.deleted = %s', $targetTableAlias, $param);
}
return $filter;
}
}
But when I'm trying to set array of posible filter values in controller
$filters
->enable('costs_item_deleted')
->setParameter('deleted', [true, false]);
I get an error 500
Warning: PDO::quote() expects parameter 1 to be string, array given
This is clear situation. But, after all HOW to send array of params to my SQL filter?
UPD after Dmitry answer: This is not actualy what I wanted. Let say: what if I wanted to filter by few values of field? For records of 2015 and 2016 years for example... So i need to set some sort of array-of-years in ->setParameter. But it want only strings! And sends an error when i'm trying to set something else.
How do you solve this?
Or even more complicated example. What if I need to filter by relational field. In this case I need to set entity as param of filter. Or even ArrayCollection of entities!
For now I'm decide it like this: I json_encode array before set it to setParameter in controller. And then I json_decode it in Filter class. BUT! There in Filter class I need to make one more step. I need to remove single and doublequotes from json string. Because they was added by setParameter to escape string (thats why we love it )) ).
Code hacks like this we call "crutches" here in Russia. So I'd like to avoid of them and write more elegant code )
Make it IN instead of comparing.
foreach ($fdata as $param) {
$filter .= sprintf('OR %s.deleted IN (%s)', $targetTableAlias, param);
}

Change date format in xls export with Sonata Admin Bundle

I taken over responsibility for a Symfony2 application, built on the Sonata Admin Bundle, and have been asked to make a small change by the users. In the xls export of a list page, the dates all appear as e.g. Wed, 01 Aug 2012 00:00:00 +0200, but the Excel format is General. The users would like the data in this column to be an Excel date type, so that it is sort-able.
I have been able to find some information about export customization, but this mostly concerns choosing the list export file types, or which fields to include, rather than how to change the format in the exported document. A similar question was asked here (I think) but there is no answer.
I think this would (or should) be very simple, but it is certainly not obvious. Any help would be much appreciated.
A small improvement for Marciano's answer.
Makes the code a bit more resilient against sonata updates.
public function getDataSourceIterator()
{
$datasourceit = parent::getDataSourceIterator();
$datasourceit->setDateTimeFormat('d/m/Y'); //change this to suit your needs
return $datasourceit;
}
In my admin class EmployeeAdmin I use getExportFields function specifies which fields we want to export:
public function getExportFields() {
return array(
$this->trans('list.label_interview_date') => 'interviewDateFormatted'
);
}
interviewDateFormatted is actually a call to the corresponding entity (Employee) method getInterviewDateFormatted which looks like this:
public function getInterviewDateFormatted() {
return ($this->interviewDate instanceof \DateTime) ? $this->interviewDate->format("Y-m-d") : "";
}
This way I can change date format or do other necessary changes to the fields I want to export.
this is my code. It's work!
use Exporter\Source\DoctrineORMQuerySourceIterator;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
and function:
/**
* {#inheritdoc}
*/
public function getDataSourceIterator()
{
$datagrid = $this->getDatagrid();
$datagrid->buildPager();
$fields=$this->getExportFields();
$query = $datagrid->getQuery();
$query->select('DISTINCT ' . $query->getRootAlias());
$query->setFirstResult(null);
$query->setMaxResults(null);
if ($query instanceof ProxyQueryInterface) {
$query->addOrderBy($query->getSortBy(), $query->getSortOrder());
$query = $query->getQuery();
}
return new DoctrineORMQuerySourceIterator($query, $fields,'d.m.Y');
}
just add this in your admin (overriding a method of the admin class you are extending). Found it reading the code. It's not in the docs.
public function getDataSourceIterator()
{
$datagrid = $this->getDatagrid();
$datagrid->buildPager();
$datasourceit = $this->getModelManager()->getDataSourceIterator($datagrid, $this->getExportFields());
$datasourceit->setDateTimeFormat('d/m/Y'); //change this to suit your needs
return $datasourceit;
}
Did you managed to make it work?
Date format is defined as parameter for new DoctrineORMQuerySourceIterator.php (https://github.com/sonata-project/exporter/blob/master/lib/Exporter/Source/DoctrineORMQuerySourceIterator.php)
DoctrineORMQuerySourceIterator.php is created inside getDataSourceIterator function (https://github.com/sonata-project/SonataDoctrineORMAdminBundle/blob/2705f193d6a441b9140fef0996ca392887130ec0/Model/ModelManager.php)
Inside of Admin.php there is function calling it:
public function getDataSourceIterator()
{
$datagrid = $this->getDatagrid();
$datagrid->buildPager();
return $this->getModelManager()->getDataSourceIterator($datagrid, $this->getExportFields());
}
If you write your own getDataSourceIterator() then you can change date format.
Since sonata-admin 4.0, the function getDataSourceIterator() is tagged as final, so you can't override it.
So you need to create a decorating iterator :
<?php
namespace App\Service\Admin;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Exporter\DataSourceInterface;
use Sonata\DoctrineORMAdminBundle\Exporter\DataSource;
use Sonata\Exporter\Source\DoctrineORMQuerySourceIterator;
use Sonata\Exporter\Source\SourceIteratorInterface;
class DecoratingDataSource implements DataSourceInterface
{
private DataSource $dataSource;
public function __construct(DataSource $dataSource)
{
$this->dataSource = $dataSource;
}
public function createIterator(ProxyQueryInterface $query, array $fields): SourceIteratorInterface
{
/** #var DoctrineORMQuerySourceIterator $iterator */
$iterator = $this->dataSource->createIterator($query, $fields);
$iterator->setDateTimeFormat('Y-m-d H:i:s');
return $iterator;
}
}
And add it in your config/services.yaml
services:
...
App\Service\Admin\DecoratingDataSource:
decorates: 'sonata.admin.data_source.orm'
arguments: ['#App\Services\Admin\DecoratingDataSource.inner']
Found here : https://docs.sonata-project.org/projects/SonataDoctrineORMAdminBundle/en/4.x/reference/data_source/

Right form events to display modified data and update modified data?

A simple task: before displaying the form, if $data->getRole() starts with "ROLE_", remove this string and display only the rest. When user submit the form, do the opposite: add "ROLE_" before the name.
What's the best place to do this? Actually i'm using PRE_SET_DATA and POST_BIND. Are these the right events to perform this operation?
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function(DataEvent $event){
if(is_null($data = $event->getData()) || !$data->getId()) return;
$data->setRole(strtoupper(preg_replace('/^ROLE_/i', '',
$data->getRole())));
});
$builder->addEventListener(FormEvents::POST_BIND,
function(DataEvent $event) {
if(is_null($data = $event->getData()) || !$data->getId()) return;
$data->setRole('ROLE_' . strtoupper($data->getRole()));
});
Well reading the role without the prefix "ROLE" is not something I would do using events. As they obsfusicate your workflow, events should be used with care! Working with symfony for some time, I used them once or twice when there was really no other way. All the other times there was a better way.
I would tend to simply add a function getShortRole and setShortRole and use shortRole within your Entity:
class MyEntity {
private $role;
public function setShortRole($role) {
$this->role = 'ROLE_' . strtoupper($role);
}
public function getShortRole() {
return strtoupper(preg_replace('/^ROLE_/i', '', $this->role));
}
}
You are saving yourself a lot of trouble working with models instead of events!
A second, more complicated way would be to use a Model which represents the form instead of the Entity and maps the form to the entity. Here is a good article about this here!
I use it myself and it works nice.

Virtual fields in Symfony2

I am still thinking about the best way to work with tags in Symfony. I did look at FPNTagBundle, but I didn't find an easy way to work this into the CRUD forms.
I also found http://xoxco.com/clickable/jquery-tags-input which would give a perfect widget. As it in- and outputs comma separated strings, I thought I could just define a virtual field in my model, that displays the tag object array as such a list.
public function addTag(\Wein\StoreBundle\Entity\Tag $tag)
{
$this->tag[] = $tag;
$this->makeTagFieldFromTags();
}
public function setTagField($tagField)
{
$this->tagField = $tagField;
$this->makeTagsFromTagField();
}
public function makeTagsFromTagField()
{
$tags=explode(',', $this->tagField);
$tagObjects=array();
$em = $this->getDoctrine()->getEntityManager();
foreach($tags as $tag) {
$tag=trim($tag);
$tagObject = **???**;
$tagObjects[]=$tagObject;
}
$this->tag=$tagObjects;
}
public function makeTagFieldFromTags()
{
$tags=array();
foreach($this->tag as $tag) {
$tags[]=$tag->__toString();
}
$this->tagField = implode(',', $tags);
}
The I could just use a form element on this field. Unfortunatly, I don't see a way to translate the strings into Tag-objects insite the entity, as I don't have access to the entity manager.
So what is the clean way?
The clean way is to use a data transformer. It transforms the strings into your tag entities at the "form" side and not in the entity, so you can keep your entity clean.

recursive function using joomla db object

I want to write a recursive function in joomla that get all the child level categories using a category id using joomla's jmodel's db object.Following is my code that I have written:
function getChildCategories($type){
$query = "SELECT id FROM #__cd_categories WHERE parent_id='$type'";
echo $query."<br/>";
$this->_db->setQuery($query);
$list = $this->_db->loadObjectList();
if ($this->_db->getErrorNum()) { echo $this->_db->stderr(); return false; }
foreach($list as $record){
$this->childCategories[]= $record->id;
echo $record->id."<br/>";
return $this->getChildCategories($record->id);
}
return true;
}
So now problem is that, in joomla we use $this->_db_setQuery method and $this->_db->loadObjectList method , so in recursive call the result set, I think it overwrite, I think because the object is same. So can any one tell the way that how to overcome this problem? If you can solve this by using loop even that would be also very helpful for me.
I also think that once values are assigned to $list variable then that over write shouldn't be problem.So seems strange.Please tell if some one can tell me the way to do it?
thanks in advance
I don't think the issue is with the database object getting overwritten. It has been a bit since I have been struggling with recursive functions but I think the issue is with assigning the $list variable.
Should you not be returning that variable instead of boolean true like this:
function getChildCategories($type) {
$query = "SELECT id FROM #__cd_categories WHERE parent_id='$type'";
$this->_db->setQuery($query);
$list = $this->_db->loadObjectList();
if ($this->_db->getErrorNum()) { echo $this->_db->stderr(); return false; }
if ($list) {
foreach($list as $record){
$list->childCategories = $this->getChildCategories($record->id);
}
return $list;
} else {
return;
}
}

Resources