Laravel 5.6 - Using Traits - php

In my project, I have duplicated functions in my Controllers as each Controller needs to have similar functionality but it feels slightly dirty to repeat code.
In both my EventController and my ArticleController I have a duplicated function called handleTags() that literally does the same thing in each model.
The code looks like this:
/**
* Handle tagging of this resource
*
* First, get the tags field and make sure it isn't empty
* Convert the string into an array and loop through the array
* Create new tags if they don't exist
*
* #param Request $request: data from the request
* #param int $id: the ID of the model instance have tags synced to
*/
public function handleTags(Request $request, $id)
{
$event = Event::find($id);
if ($request->has('tags')) {
$tags = $request->get('tags');
if (!empty($tags)) {
// Turn a String into an array E.g. one, two
$tagArray = array_filter(explode(", ", $tags));
// Loop through the tag array that we just created
foreach ($tagArray as $tag) {
Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}
// Grab the IDs for the tags in the array
$tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
$event->tags()->sync($tags);
} else {
// If there were no tags, remove them from this model instance
$event->tags()->sync(array());
}
}
}
Would it be possible to move this functionality into a Trait? Something like Taggable?
Then you would call handleTags() in the relevant Controllers via the trait, in the same way Searchable gives you access to the search() method?

I think that a better solution will be to make a Model trait, I will explain my self.
trait HasTags {
public function handleTags($tags)
{
$tags = array_filter(explode(", ", $tags))
$tags = array_map(function () {
return Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}, $tags)
$this->tags()->sync(collect($tags)->pluck('id'))
}
public function tags()
{
return $this->morphMany(Tag::class);
}
}
Model
class Event extends Model
{
use HasTags;
}
Controller
$event = Event::find($id);
if ($request->has('tags')) {
$event->handleTags($request->get('tags'));
}
I Write it very quickly and without testing it but this is the general idea.
you can event have more refactoring by using all the array manipulations with collections.

You can make a trait in app/Http/Traits/TaggableTrait.php
You just need to pass an object instead of the id, so that the function will be independent from the class type.
then your trait will be something like this :
namespace App\Http\Traits;
use App\Tag;
trait TaggableTrait
{
/**
* #param Request $request: data from the request
* #param App\Article | App\Event $object: the model instance have tags synced to
*/
public function handleTags(Request $request, $object)
{
if ($request->has('tags')) {
$tags = $request->get('tags');
if (!empty($tags)) {
// Turn a String into an array E.g. one, two
$tagArray = array_filter(explode(", ", $tags));
// Loop through the tag array that we just created
foreach ($tagArray as $tag) {
Tag::firstOrCreate([
'name' => ucfirst(trim($tag)),
'slug' => str_slug($tag)
]);
}
// Grab the IDs for the tags in the array
$tags = Tag::whereIn('name', $tagArray)->get()->pluck('id');
$object->tags()->sync($tags);
} else {
// If there were no tags, remove them from this model instance
$object->tags()->sync(array());
}
}
}
}
EventController
use App\Http\Traits\TaggableTrait;
class EventController extends Controller
{
use TaggableTrait;
/*** ***** */
public function update(Request $request, $id)
{
/** ***/
$event = Event::findOrFail($id);
handleTags($request, $event);
/*** *** */
}
}

Related

Symfony: Dynamic route for multiple entities

Currently working with Symfony 5.2
I am trying to create a dynamic route for multiple enties which have mostly same properties (some still have other fields too, but all have the same default properties).
Example (minified)
Entity News:
- id, title, author
Entity Event:
- id, title, author
I am now trying to create a dynamic route to fetch News or Events from my the repository.
class ContentController extends AbstractController {
/**
* #Route("/{type}", name="get_content")
*/
public function get(string $type) { //type is the name of the entity e.g. 'news' or 'event'
//fetch from repo
$results = $this->getDoctrine()->getRepository(/* entity class */)->findAll();
}
}
I already came up with some ideas, but not sure if they are good practise.
1) use full namespace for the entity (how do I check if the class exists? error handling?)
$results = $this->getDoctrine()->getRepository('App\\Entity\\News')->findAll();
$results = $this->getDoctrine()->getRepository('App\\Entity\\' . ucfirst($type))->findAll();
2) provide a route for each entity and call a generic function (not exactly what I want because I have like 10+ entities for this)
class ContentController extends AbstractController {
/**
* #Route("/news", name="get_news")
*/
public function get_news() {
$results = getContent(News::class);
}
/**
* #Route("/event", name="get_event")
*/
public function get_event() {
$results = getContent(Event::class);
}
public function getContent($class) {
return $this->getDoctrine()->getRepository($class)->findAll();
}
}
Mabye some of you have better ideas/improvements and can help me out a bit.
You can define available for fetching entities manually.
class ContentController extends AbstractController
{
/**
* #Route("/{type}", name="get_content")
*/
public function get(string $type)
{ //type is the name of the entity e.g. 'news' or 'event'
//fetch from repo
$results = $this->getDoctrine()->getRepository($this->getEntityClassFromType($type))->findAll();
}
private function getEntityClassFromType(string $type): string
{
//define your entities manually
foreach ([News::class, Event::class] as $class) {
$parts = explode('\\', $class);
$entity = array_pop($parts);
if ($type === lcfirst($entity)) {
return $class;
}
}
throw new NotFoundHttpException();
}
}
Or check your entities dynamically using $entityManager->getMetadataFactory()->hasMetadataFor($className);

How to generalize a resource function to be used in all controllers for different models?

In laravel API Resources:
I need a dynamic way to generalize a code for all resources to be used in all controllers instead of using resources in all methods for each controller .. for more clarification, I have a trait that includes generalized functions which return json responses with data and status code, lets take a "sample function" suppose it is showAll(Collection $collection) which is used for returning a collection of data of the specified model for example it is used for returning all users data ..
so I need to build a function that call what ever resource of the specified model, knowing that I have many models...
a) trait that include showAll method:
namespace App\Traits;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code) {
return response()->json($data, $code);
}
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$collection, 'code' => $code], $code);
}
protected function resourceData(Collection $collection) {
return $collection;
}
}
b) usercontroller as a sample
namespace App\Http\Controllers\User;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class UserController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
// Here the showAll(Collection $collection) is used
return $this->showAll($users);
}
}
c) UserResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'identity' => $this->id,
'name' => $this->name,
'email' => $this->email,
'isVerified' => $this->verified,
'isAdmin' => $this->admin,
'createDate' => $this->created_at,
'updateDate' => $this->updated_at,
'deleteDate' => $this->deleted_at,
];
}
}
generalize: means used everywhere without code redundancy
What about providers, you may load data there and make that data reachable at places where user data can be reachable ?
laravel docs
I found a simple solution.. by adding the following method
protected function resourceData($collection) {
$collection = get_class($collection[0]);
$resource = 'App\Http\Resources\\' . str_replace('App\\', '', $collection) .
'Resource';
return $resource;
}
The $collection[0] in the first line of this method will get the
model you are currently using.
get_class will get the model name ex: App\User
'App\Http\Resources\\' . str_replace('App\\', '', $collection):
This will get the path of the resource by adding 'App\Http\Resources\' before the
model
str_replace('App\\', '', $collection): will remove App\ path from the collection
name so App\User should be User
then 'Resource' would be concatenated with the previous results and the whole
string should be like that: App\Http\Resources\UserResource
So at the end you should return the whole string App\Http\Resources\UserResource
,finally you should call the resourceData() in
the showAll() method:
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
//Calling resourceData() method
$resource = $this->resourceData($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$resource::collection($collection), 'code' => $code], $code);
}

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()
);
}

Laravel method that can be used by multiple controllers and commands. Where should it be?

I'd like to define a "global" method that can be used by multiple controllers and commands. Where should it be placed in Laravel 5.4?
Let's say I have the following controller. How would I call the "global" method instead, and where would that "global" method be located exactly?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Flight;
class FlightsController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Index
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$flights = Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
foreach ($flights as $flight) {
if ( $flight->price == 0 )
{
$output = "some value";
}
else
{
$output = "some other value";
}
}
return view('flights.index')
->with(['output' => $output])
;
}
}
When you want a method that fetches many models, and you want to use it in many places, put it in a Repository:
class FlightRepository
{
public function getLastTenFlights()
{
return Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
}
}
For example from your controller:
public function index( FlightRepository $repo )
{
$flights = $repo->getLastTenFlights();
//if you want you can put this additional login in the method too...
foreach ($flights as $flight) {
if ( $flight->price == 0 )
{
$output = "some value";
}
else
{
$output = "some other value";
}
}
return view('flights.index')
->with(['output' => $output])
;
}
You can create a Object and call the object when you want.
See example:
FlighRepository = new FlighRepository;
FlighRepository->index();
I personally prefer query scopes to repositories, so I would do something like this:
class Flight extends Model
{
// model setup
/**
* Scope query to get last 10 flights.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeLastTen($query)
{
return $query->where('active', 1)->orderBy('name', 'desc')->take(10);
}
// rest of model
}
And you can use it similarly to how you're currently using it, only it's more readable:
$flights = Flight::lastTen()->get();
This also has the advantage of being able to chain other queries off of it. Say, for example, you wanted the last ten American Airlines flights, you could do:
$flights = Flight::lastTen()->where('airline', 'American')->get();
// equivalent to
// $flights = Flight::where('airline', 'American')->lastTen()->get();
I think that service is the best option to store the functionality which is shared between controllers and commands. You can access them using Service Container (https://laravel.com/docs/5.5/container).

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