Convert an array of objects into an array of DTOs - php

I am currently working on a HMS project (Hotel Management System). I am currently stucked in implementing a DTO based functionality in Symfony 5. The following from below is my HotelMapper.php file where I would like to build a method which transforms the Array of Hotels into an Array of DTOs so I can pass them in the Hotel Controller later on and for this I would like to use the objects from the dtoToHotel() function. I already created the DTO (setters & getters).
namespace App\Transformer;
use App\DTO\HotelDTO;
use App\Entity\HotelEntity;
class HotelMapper
{
public $hotel;
public function dtoToHotel(HotelDTO $hotelDTO, Hotel $hotel) : HotelEntity
{
$hotel->setId($hotelDTO->getId());
$hotel->setName($hotelDTO->getName());
$hotel->setLocation($hotelDTO->getLocation());
$hotel->setEmployees($hotelDTO->getEmployees());
$hotel->setAvailability($hotelDTO->getAvailability());
$hotel->setFacility($hotelDTO->getFacility());
$hotel->setPhoto($hotelDTO->getPhoto());
$hotel->setDescription($hotelDTO->getDescription());
$hotel->setEarnings($hotelDTO->getEarnings());
}
public function hotelToDto(HotelEntity $hotel)
{
return HotelDTO(
$hotel->getId(),
$hotel->getName(),
$hotel->getLocation(),
$hotel->getEmployees(),
$hotel->getAvailability(),
$hotel->getFacility(),
$hotel->getPhoto(),
$hotel->getDescription(),
$hotel->getEarnings()
);
}
public function transformHotelsArrayToDTO()
{
/* Code here */
}
}
The code from below is my HotelController where I would like to update the following line $hotels = $this->hotelRepository->findAll() inside the showAllHotels() function by passing the DTO in here. Any help is much appreciated!
class HotelController extends AbstractController
{
/**
* #var HotelRepository
*/
public $hotelRepository;
public function __construct(HotelRepository $hotelRepository)
{
$this->hotelRepository = $hotelRepository;
}
/**
* #Route (path="/", methods={"GET"})
*/
public function index(): Response
{
return $this->render('index/index.html.twig');
}
/**
* #Route (path="/hotel-management", methods={"GET"})
*/
// It does populate the table with the hotels from the DB
public function showAllHotels(): Response
{
$hotels = $this->hotelRepository->findAll();
return $this->render('hotel-management/hotel-management.html.twig', array('hotels' => $hotels));
}
}

Inject HotelMapper transformer to your controller and then pass findAll's result to this function:
public function transformHotelsArrayToDTO(array $items): array
{
if (empty($items) or is_null($items)) {
return [];
}
return array_map([$this, 'hotelToDto'], $items);
}

Related

How to add additional fields into a final JSON through accessors?

I need to add an empty collection that will always be. I do it.
protected $appends = ['additional'];
public function getAdditionalAttribute()
{
return collect();
}
Then I do additional accessor, for example
public function getSomeAttributeAttribute()
{
//some code
return $something; //bool
}
Then in the right place I call this accessor ->append('some_attribute');
But I need that the result was inside in the collection additional.
I try do it like this:
public function getSomeAttributeAttribute()
{
//some code
return $this->additional['some_attribute'] = $something; //bool
}
But it does not work, and the result is on the same level with all the elements, not inside of the collection additional.
I can do something like this:
public function getAdditionalAttribute()
{
return collect([
'some_attribute' => $this->some_attribute
]);
}
It works, but this value will be constantly, but I want to call it only when it is necessary through ->append('some_attribute');
But the collection to be constantly although empty.
Or maybe there is a different way to make it. How can I do it?
Maybe it's easier for you to overwrite the serialization itself:
class YourModel extends Model
{
/**
* Convert the object into something JSON serializable.
*
* #return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return [
'additional' => [
'some_attribute' => $something
]
];
}
}
If you need the collection, you may want to use a custom one (php artisan make:collection \\App\\Collections\\AdditionalCollection):
class YourModel extends Model
{
public function getAdditionalAttribute()
{
return new App\Collections\AdditionalCollection($something);
}
}
class AdditionalCollection extends Collection
{
private $something;
public function __construct($something)
{
$this->something = $something;
parent::__construct([]);
}
public function __get(string $property)
{
if ($property === 'some_attribute') {
return $this->something;
}
}
}

Laravel accessor returning values of nested relationships which are not required

Currently I'm stuck in making an accessor. I'm trying to access some values from the nested relationship after when I got that I'm returning the value and appending it to the model, but the problem is inside my response I'm getting values of the relationship which I try to access in my accessor.
public function getTranslatorEmailAttribute()
{
if (in_array(AddOnConfirmation::EMAIL, $this->customer->department->company->add_on_confirmation)) {
return $this->assignedTranslator()->first()->email;
} else {
return null;
}
}
Here is the customer relation which I'm trying to use
public function customer()
{
return $this->belongsTo(User::class)->with('customerData.customerType', 'customerData.department.company');
}
How can I fix this?
Here is a screenshot of response I'm getting with using accessor
Your GET route that handles api/bookings/{id} should return the resource in the end, something like:
return BookingResource::make($booking);
Then create BookingResource and likely put it in namespace App\Http\Resources\Api;
The file itself can look like:
namespace App\Http\Resources\Api;
use Illuminate\Http\Resources\Json\Resource;
/** #mixin \App\Models\Booking */
class BookingResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
$append = $request->get('append');
return [
// Here you'd put everything you want to show
'id' => $this->id,
'translator_email' => $append == 'translator_email' ? $this->translator_email : null,
// ...
];
}
}

Laravel Nova - Reorder left navigation menu items

In default the ordering of left menu items is in alphabetical order.
My client wants to order those menus manually. Any idea how to make it possible?
Go to answer
You can do it in
App\Providers\NovaServiceProvider.php
add a method resources() and register the resources manually like
protected function resources()
{
Nova::resources([
User::class,
Post::class,
]);
}
Alternate
There is another way mentioned in this gist, this seems good too, but official documentation has no mention of it yet.
Resource
<?php
namespace App\Nova;
class User extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = 'App\\User';
/**
* Custom priority level of the resource.
*
* #var int
*/
public static $priority = 1;
// ...
}
and in NovaServiceProvider
<?php
namespace App\Providers;
use Laravel\Nova\Nova;
use Laravel\Nova\Cards\Help;
use Illuminate\Support\Facades\Gate;
use Laravel\Nova\NovaApplicationServiceProvider;
class NovaServiceProvider extends NovaApplicationServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
Nova::sortResourcesBy(function ($resource) {
return $resource::$priority ?? 99999;
});
}
}
This way you set priority of resource and based on priority you render the resource.
A cleaner way and tested on latest Nova 3.x. Also, this has been added to Nova since version 2.10+ All you need to do is add a static property on your nova classes. For example Clients will be:
/**
* The side nav menu order.
*
* #var int
*/
public static $priority = 2;
Then after that you can use the NovaServiceProvider to tell nova to use your custom ordering. You can place the code in the boot method
public function boot()
{
Nova::sortResourcesBy(function ($resource) {
return $resource::$priority ?? 9999;
});
}
**Reference Nova Private Repo
There are two ways to achieve this:
By setting priority to Resource
Ordering Resource models in NovaServiceProvider
1. Priority Method
Add priority as in the following code in Resource model:
public static $priority = 2;
Then update NovaServiceProvider like this:
public function boot()
{
Nova::sortResourcesBy(function ($resource) {
return $resource::$priority ?? 9999;
});
}
2. Ordering Resource models in NovaServiceProvider
In NovaServiceProvider, order Resource models like this:
protected function resources()
{
Nova::resources([
User::class,
Post::class,
]);
}
you can use grouping if that helps. I know it's not a 100% fix but maybe it will help a bit.
public static $group = 'Admin';
Change /nova/resources/navigation.blade.php {{ $group }} to following:
{!! $group !!}
Now you can easily sort the groups as follows:
public static $group = '<span class="hidden">20</span>Music';
or
public static $group = '<span class="hidden">30</span>User';
Attention: You must convert special characters in the title!
With the links, it's a bit other....
First Method: dirty and ugly
You can change
{{ $resource::label() }}
to
{{ substr($resource::label(), 1) }}
Then you can sort the links by the first letter of the resource name.
AUser
BAlbum
CContact
Or a better Method for Links
crate app/Nova/CustomResource.php:
<?php
namespace App\Nova;
use Illuminate\Support\Str;
abstract class CustomResource extends Resource
{
public static $label = '';
/**
* #return string
*/
public static function label()
{
if(static::$label) {
return static::$label;
}
return Str::plural(Str::title(Str::snake(class_basename(get_called_class()), ' ')));
}
}
Change /nova/resources/navigation.blade.php
{!! $resource::label() !!}
And in the Nova resource, extends this custom resource and You can use public static $label:
class Lyric extends CustomResource
{
public static $label = '<span class="hidden">10</span>Lyrics';
public static function singularLabel()
{
return __('Lyric');
}
Attention: You must convert special characters in the title!
to sort the groups:
add this to your resources:
public static function groupOrder() {
return 9999999;
}
you can overwrite it by adding it to any member resource to downgrade it's order in the navigation tree:
public static function groupOrder() {
return 5;
}
add this before returning at the end of resourcemanager (i hope i shouldn't have to overwrite this at this place):
$arrSort = [];
foreach ($navigation as $group => $resources) {
$resourcesGruoupOrders = [];
foreach ($resources as $aResource) {
$resourcesGruoupOrders[] = $aResource::groupOrder();
}
$arrSort[] = min($resourcesGruoupOrders);
}
$navigation = json_decode(json_encode($navigation), true);
array_multisort($navigation, SORT_ASC, SORT_NUMERIC, $arrSort);
If You wondering how to sort groups using a custom sort algorithm here is the clean solution.
In NovaServiceProvider in boot() method just add a custom callback.
$order = array_flip(['Modules', 'Localization', 'Other', 'Settings']);
Nova::mainMenu(static function (Request $request, Menu $menu) use ($order): Menu {
$resources = $menu->items->firstWhere('name', 'Resources');
$resources->items = $resources->items->sort(
fn (MenuGroup $a, MenuGroup $b): int => ($order[$a->name] ?? INF) <=> ($order[$b->name] ?? INF)
);
return $menu;
});
Using $order array you can easily control the position of every specific group. Groups that are not included in this array will be moved to the end of the menu. This behavior can be changed to a moving to the beginning by replacing INF with -INF.
Before add to resource static property
public static $priority = 1;
Then in NovaServiceProvider replace resource method
protected function resources()
{
$namespace = app()->getNamespace();
$resources = [];
foreach ((new Finder)->in(app_path('Nova'))->files() as $resource) {
$resource = $namespace.str_replace(
['/', '.php'],
['\\', ''],
Str::after($resource->getPathname(), app_path().DIRECTORY_SEPARATOR)
);
if (is_subclass_of($resource, Resource::class) &&
! (new \ReflectionClass($resource))->isAbstract()) {
$resources[] = $resource;
}
}
Nova::resources(
collect($resources)->sort(function ($a, $b) {
return $a::$priority > $b::$priority;
})->all()
);
}

TYPO3 - how to get FE UID via Viewhelper

How can I fetch and render the uid of the FE User via a Viewhelper? The below is working via a Controller ... but not in a Viewhelper. Where is the difference? I'm using 7.6.11 and at the end I would like to have the uid of the FE User and the usergroup uid of him and further use it in the html of the extension and in general partials ...
/typo3conf/ext/extension/Classes/ViewHelpers/UserViewHelper.php
<?php
namespace Vendor\Extension\ViewHelpers;
class UserViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* User Repository
*
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
* #inject
*/
protected $userRepository;
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserGroupRepository
* #inject
*/
protected $frontendUserGroupRepository;
public function render() {
$userIDTest = $this->userRepository->findByUid($GLOBALS['TSFE']->fe_user->user['uid']);
$this->view->assign('userIDTest', $userIDTest);
}
}
List.html
<f:layout name="Default" />
<f:section name="main">
{userIDTest.uid}
</f:section>
As suggested by Dimitry I replaced
$this->view->assign('userIDTest', $userIDTest);
with
return $userIDTest;
And in List.html I have this:
{namespace custom=Vendor\Extension\ViewHelpers}
<f:layout name="Default" />
<f:section name="main">
<f:alias map="{user: '{custom:user()}'}">
{user.uid} {user.username}
</f:alias>
</f:section>
... and after clearing all Caches (FE/BE/Install) and deleting typo3temp ... now its working!
In 7.x and upwards ViewHelpers are compiled, resulting in the render method being called only once for compiling. Afterwards, only the static method renderStatic() is called. You could overwrite renderStatic, it will be called every time:
<?php
namespace Vendor\Extension\ViewHelpers;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class UserIdViewHelper extends AbstractViewHelper
{
public function render()
{
return static::renderStatic(
[],
$this->renderChildrenClosure,
$this->renderingContext
);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$userData = $GLOBALS['TSFE']->fe_user->user;
return null !== $userData ? (int)$userData['uid'] : null;
}
}
If you need to use some service in your ViewHelper, things get more complicated, since dependency injection won't work with compiled ViewHelpers. You need to get an object manager, and fetch an instance of the service with the object manager.
This could look like this, assuming you would want to use the FrontendUserRepository as service, because you want to return the entire user object, not only the users uid:
<?php
namespace Vendor\Extension\ViewHelpers;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
class UserViewHelper extends AbstractViewHelper
{
/**
* #var FrontendUserRepository
*/
private static $frontendUserRepository = null;
public function render()
{
return static::renderStatic(
[],
$this->renderChildrenClosure,
$this->renderingContext
);
}
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$userData = $GLOBALS['TSFE']->fe_user->user;
if (null === $userData) {
return null;
}
return static::getFrontendUserRepository()->findByUid((int)$userData['uid']);
}
private static function getFrontendUserRepository()
{
if (null === static::$frontendUserRepository) {
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
static::$frontendUserRepository = $objectManager->get(FrontendUserRepository::class);
}
return static::$frontendUserRepository;
}
}
Disclaimer: All the code is written without actually running it, thus there are bugs in it.
if you want to return the user or the uid of the user in the viewhelper, just return it.
instead of
$this->view->assign('userIDTest', $userIDTest);
do this
return $userIDTest;
In your fluid you can use the user variables in different ways. The easiest one is to use the "alias" viewhelper: https://fluidtypo3.org/viewhelpers/fluid/master/AliasViewHelper.html
<f:alias map="{user: '{namespace:user()}'}">
{user.uid} {user.username}
</f:alias>

Encryption/Decryption of Form Fields in CakePHP 3

I want to have some form-fields encrypted when they are added/edited and decrypted when they are looked up by cake.
Here is the code that works for me in v2.7.2:
core.php
Configure::write('Security.key','secretkey');
app/model/patient.php.
public $encryptedFields = array('patient_surname', 'patient_first_name');
public function beforeSave($options = array()) {
foreach($this->encryptedFields as $fieldName){
if(!empty($this->data[$this->alias][$fieldName])){
$this->data[$this->alias][$fieldName] = Security::encrypt(
$this->data[$this->alias][$fieldName],
Configure::read('Security.key')
);
}
}
return true;
}
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
foreach($this->encryptedFields as $fieldName) {
if (#is_array($results[$key][$this->alias])) {
$results[$key][$this->alias][$fieldName] = Security::decrypt(
$results[$key][$this->alias][$fieldName],
Configure::read('Security.key')
);
}
}
}
return $results;
}
As I understand it I have to replace $this->data[] with the generated entities for the model and the afterFind method with virtual fields, but I just can't put it all together.
There's more than one way to solve this (please note that the following code is untested example code! You should get a grasp on the new basics first before using any of this).
A custom database type
One would be a custom database type, which would encrypt when binding the values to the database statement, and decrypt when results are being fetched. That's the option that I would prefer.
Here's simple example, assuming the db columns can hold binary data.
src/Database/Type/CryptedType.php
This should be rather self explantory, encrypt when casting to database, decrypt when casting to PHP.
<?php
namespace App\Database\Type;
use Cake\Database\Driver;
use Cake\Database\Type;
use Cake\Utility\Security;
class CryptedType extends Type
{
public function toDatabase($value, Driver $driver)
{
return Security::encrypt($value, Security::getSalt());
}
public function toPHP($value, Driver $driver)
{
if ($value === null) {
return null;
}
return Security::decrypt($value, Security::getSalt());
}
}
src/config/bootstrap.php
Register the custom type.
use Cake\Database\Type;
Type::map('crypted', 'App\Database\Type\CryptedType');
src/Model/Table/PatientsTable.php
Finally map the cryptable columns to the registered type, and that's it, from now on everything's being handled automatically.
// ...
use Cake\Database\Schema\Table as Schema;
class PatientsTable extends Table
{
// ...
protected function _initializeSchema(Schema $table)
{
$table->setColumnType('patient_surname', 'crypted');
$table->setColumnType('patient_first_name', 'crypted');
return $table;
}
// ...
}
See Cookbook > Database Access & ORM > Database Basics > Adding Custom Types
beforeSave and result formatters
A less dry and tighter coupled approach, and basically a port of your 2.x code, would be to use the beforeSave callback/event, and a result formatter. The result formatter could for example be attached in the beforeFind event/callback.
In beforeSave just set/get the values to/from the passed entity instance, you can utilize Entity::has(), Entity::get() and Entity::set(), or even use array access since entities implement ArrayAccess.
The result formatter is basically an after find hook, and you can use it to easily iterate over results, and modify them.
Here's a basic example, which shouldn't need much further explanation:
// ...
use Cake\Event\Event;
use Cake\ORM\Query;
class PatientsTable extends Table
{
// ...
public $encryptedFields = [
'patient_surname',
'patient_first_name'
];
public function beforeSave(Event $event, Entity $entity, \ArrayObject $options)
{
foreach($this->encryptedFields as $fieldName) {
if($entity->has($fieldName)) {
$entity->set(
$fieldName,
Security::encrypt($entity->get($fieldName), Security::getSalt())
);
}
}
return true;
}
public function beforeFind(Event $event, Query $query, \ArrayObject $options, boolean $primary)
{
$query->formatResults(
function ($results) {
/* #var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
return $results->map(function ($row) {
/* #var $row array|\Cake\DataSource\EntityInterface */
foreach($this->encryptedFields as $fieldName) {
if(isset($row[$fieldName])) {
$row[$fieldName] = Security::decrypt($row[$fieldName], Security::getSalt());
}
}
return $row;
});
}
);
}
// ...
}
To decouple this a little, you could also move this into a behavior so that you can easily share it across multiple models.
See also
Cookbook > Database Access & ORM > Database Basics > Adding Custom Types
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields
Cookbook > Tutorials & Examples > Bookmarker Tutorial Part 2 > Persisting the Tag String
Cookbook > Database Access & ORM > Behaviors
API > \Cake\Datasource\EntityTrait
API > \Cake\ORM\Table
Edit: #npm was right about the virtual properties not working. Now i'm angry at myself for giving a bad answer. serves me right for not checking it before I posted.
To make it right, I've implemented a version using behaviors to decrypt the fields as they are read, and encrypt them as they are written to the database.
Note: This code does not currently incorporate any custom finders, so it will not support searching by the encrypted field.
eg.
$this->Patient->findByPatientFirstname('bob'); // this will not work
Behavior
/src/Model/Behavior/EncryptBehavior.php
<?php
/**
*
*/
namespace Cake\ORM\Behavior;
use ArrayObject;
use Cake\Collection\Collection;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\ResultSetInterface;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use Cake\Utility\Security;
use Cake\Log\Log;
/**
* Encrypt Behavior
*/
class EncryptBehavior extends Behavior
{
/**
* Default config
*
* These are merged with user-provided configuration when the behavior is used.
*
* #var array
*/
protected $_defaultConfig = [
'key' => 'YOUR_KEY_KERE', /* set them in the EntityTable, not here */
'fields' => []
];
/**
* Before save listener.
* Transparently manages setting the lft and rght fields if the parent field is
* included in the parameters to be saved.
*
* #param \Cake\Event\Event $event The beforeSave event that was fired
* #param \Cake\ORM\Entity $entity the entity that is going to be saved
* #return void
* #throws \RuntimeException if the parent to set for the node is invalid
*/
public function beforeSave(Event $event, Entity $entity)
{
$isNew = $entity->isNew();
$config = $this->config();
$values = $entity->extract($config['fields'], true);
$fields = array_keys($values);
$securityKey = $config['key'];
foreach($fields as $field){
if( isset($values[$field]) && !empty($values[$field]) ){
$entity->set($field, Security::encrypt($values[$field], $securityKey));
}
}
}
/**
* Callback method that listens to the `beforeFind` event in the bound
* table. It modifies the passed query
*
* #param \Cake\Event\Event $event The beforeFind event that was fired.
* #param \Cake\ORM\Query $query Query
* #param \ArrayObject $options The options for the query
* #return void
*/
public function beforeFind(Event $event, Query $query, $options)
{
$query->formatResults(function ($results){
return $this->_rowMapper($results);
}, $query::PREPEND);
}
/**
* Modifies the results from a table find in order to merge the decrypted fields
* into the results.
*
* #param \Cake\Datasource\ResultSetInterface $results Results to map.
* #return \Cake\Collection\Collection
*/
protected function _rowMapper($results)
{
return $results->map(function ($row) {
if ($row === null) {
return $row;
}
$hydrated = !is_array($row);
$fields = $this->_config['fields'];
$key = $this->_config['key'];
foreach ($fields as $field) {
$row[$field] = Security::decrypt($row[$field], $key);
}
if ($hydrated) {
$row->clean();
}
return $row;
});
}
}
Table
/src/Model/Table/PatientsTable.php
<?php
namespace App\Model\Table;
use App\Model\Entity\Patient;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Core\Configure;
/**
* Patients Model
*
*/
class PatientsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('patients');
$this->displayField('id');
$this->primaryKey('id');
// will encrypt these fields automatically
$this->addBehavior('Encrypt',[
'key' => Configure::read('Security.key'),
'fields' => [
'patient_surname',
'patient_firstname'
]
]);
}
}
I feel your pain. the ORM layer in cakephp 3 is radically different from cake2. They split the entity model and the table ORM into two different classes, and afterFind has been removed. I would take a look at using virtual properties. I think it might be suitable for your use case.
Example below.
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\Utility\Security;
use Cake\Core\Configure;
class Patient extends Entity
{
protected function _setPatientSurname($str)
{
$this->set('patient_surname', Security::encrypt($str, Configure::read('Security.key'));
}
protected function _setPatientFirstname($str)
{
$this->set('patient_firstname', Security::encrypt($str, Configure::read('Security.key'));
}
protected function _getPatientSurname()
{
return Security::decrypt($this->patient_surname, Configure::read('Security.key'));
}
protected function _getPatientFirstname()
{
return Security::decrypt($this->patient_first_name, Configure::read('Security.key'));
}
}

Categories