Is it possible to define virtual fields (SQL function calls) in entities? - mysql

Is their way, we can add MySQL custom function in Entity
protected $_virtual = ['check_tenant' => '(check_tenant(Tenants.id))'];
I would like to call following query in with find() method
//SELECT id, first_name, check_tenant(Tenants.id) FROM tenants AS Tenants
$this->Tenants->find()->all();
If I can define custom MySQL function in the virtual field then it would automatically return in the result set
I am able to pass the new field in select() method
$this->Tenants->find()
->select(['id', 'check_tenant' => '(check_tenant(Tenants.id))'])->all();
But i would like to define globally, so the new field don't need to pass in every find call

Virtual properties in CakePHP 3.x are not the same as virtual fields in CakePHP 2.x, the latter were used in SQL queries, and the former are being used on PHP level, usually with data already present in the entity.
If you want your custom field to be present in all queries, then you could for example use the Model.beforeFind() event to modify the queries accordingly:
// in TenantsTable class
public function beforeFind(\Cake\Event\Event $event, \Cake\ORM\Query $query, array $options)
{
return $query
// select custom fields (functions builder usage not required, but advised)
->select(function (\Cake\ORM\Query $query) {
return ['check_tenant' => $query->func()->check_tenant([
'Tenants.id' => 'identifier'
])];
})
// ensure that the tables default fields are being selected too
->enableAutoFields(true); // autoFields() before CakePHP 3.4
}
Another less invasive option would be custom finders, that you explicitly use where you need them:
// in TenantsTable class
public function findWithTenantCheck(\Cake\ORM\Query $query, array $options)
{
return $query
->select(/* ... */)
->enableAutoFields(true);
}
// query data
$query = $this->Tenants->find('withTenantCheck');
See also
Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks > beforeFind
Cookbook > Database Access & ORM > Entities > Creating Virtual Properties
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Using Finders to Load Data
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
Cookbook > Database Access & ORM > Query Builder > Using SQL Functions

Related

How to map database function to Entity Framework Core property

my problem is that i can't map database function to use it in object property like computed column but for different tables with relation. My relation is SizeItem to Review (one to many)
I have IEntityTypeConfiguration
internal class SizeItemConfiguration : IEntityTypeConfiguration<SizeItem>
{
public const string TableName = "size_items";
public void Configure(EntityTypeBuilder<SizeItem> builder)
{
builder.ToTable(TableName);
builder.Property(p => p.Id)
.UseHiLo("size_item_hilo");
}
}
And want to compute an average rating from Review entities related to a SizeItem.
I tried to find examples to use database functions and EF Core functions but nothing works.
How can I get computed SizeItem.AverageRating for every sql command? May be code will looks like:
builder.Property(p => Functions.Average(p.Reviews.Select(x => x.Rating)));
User-defined function mapping doesn't work for me because I'm using Repository pattern.

Laravel Eloquent supporting MariaDb dynamic column

For Dynamic column supported in Maria-DB and in MySQL we have JSON column type. For one of our projects, we should be implementing a database for Maria-DB (not Mysql).
The Dynamic Column is supported using yii2-dynamic-ar package.
how can can override Eloquent orm in Laravel to add dynamic-columns. in the Yii package which added this feature to ActiveRecord this classes can override ActiveRecord class
implementations classes in Yii framework to support in ActiveRecord ORM:
DynamicActiveRecord.php
DynamicActiveQuery.php
I just created package for handling MariaDB dynamic Column using eloquent and query builder.
To install package run this command:
composer require halalsoft/laravel-dynamic-column
You can start using the package by adding the HasDynamicColumn trait and use Dynamic as attribute cast to your models.
An example:
use Illuminate\Database\Eloquent\Model;
use Halalsoft\LaravelDynamicColumn\Dynamic;
use Halalsoft\LaravelDynamicColumn\HasDynamicColumn;
class MyModel extends Model
{
use HasDynamicColumn;
protected $casts
= [
'the_column' => Dynamic::class,
];
}
Now You can use dynamic column like json column using eloquent or query builder:
$modelData = MyModel::find(1);
$columnData = $modelData->the_column;
$columnData['data1'] = 'value';
$columnData['data2'] = 'value2';
$modelData->the_column = $columnData;
$modelData->save();
You can also create data field as array
$newData = MyModel::create([
'other_column' => 'this just another column data',
'the_column' => ['data1'=>'value1','data2'=>'value2']
]);
to update a json field/key you use, you may use the -> operator when calling the update method:
$page->update(['content->data1' => 'value1new']);
or you can still update whole column using normal array:
$page->update(['content' => ['data1'=>'value1new','data2'=>'value2new']]);
You can set as array using other method like updateOrCreate(), firstOrCreate(), etc.
This package also support query builder using:
Model::query()->where('the_column->data1', 'value1')->first();
This package is still new, if any issue or request just go to github issue
You can have a cast defined for the column in the Model class
//Model class
protected $casts = ['my_column' => 'array];
You can define the datatype for the column as text if you want or json, with the cast defined, you will be able to work with the column data as associative array.
There's also a package to add json datatype in migration for mariadb - it may be of help
https://github.com/ybr-nx/laravel-mariadb

Disable Doctrine automatic queries

With Symfony 4.2 and Doctrine, I want to disable automatic queries.
If I execute this simple example :
$posts = $em->getRepository(Post::class)->findAll();
foreach ($posts as $post) {
dump($post->getCategory()->getName();
}
Doctrine will search categories by itself. But I want to disable that. to force me to join (LEFT or INNER in repository).
It's possible ? Thanks
Implicit data fetching from database by accessing linked entity properties is one of core principles of Doctrine and can't be disabled. If you want to just fetch some data explicitly - you need to construct your own partial query and hydrate data either as array or simple object so your fetched results will not became entities.
Nothing can automatically disable this behavior and force you to write JOIN clauses, except your wishes.
This behavior (which is called lazy loading) is one of the main common behavior of all ORMs.
If you are not happy with this (and you probably have good reasons), then consider writing your own DQL queries, which are limited to your selected fields. What is not in your query will not be fetched automatically afterwards.
Write this method in your custom PostRepository class :
public function findAll(){
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p')
->from('Post', 'p');
return $qb->getQuery()->getResult();
}
Then in your controller, you can do the same as before:
$posts = $em->getRepository(Post::class)->findAll();
foreach ($posts as $post) {
dump($post->getCategory()->getName();
}
Calling the getName() method from the Category entity will now throws an error, and will not launch any hidden SQL queries behind. If you want to play with Posts and Categories together, then you can adapt your findAll() method like this :
public function findAll(){
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p, c')
->from('Post', 'p');
->join('p.category', 'c')
return $qb->getQuery()->getResult();
}

How to get tablename from Event in Behavior

I'm writing a behavior in CakePHP 3.0.11, and I can var_dump() and Log::debug() the $event object in my behavior's beforeSave() method, and can see the object data, but cannot access the object's properties.
I am simply trying to get the table (alias, className, tablename, anything) name from the object.
I would like to do something like this in my Behavior:
public function beforeSave(Event $event, Entity $entity)
{
$table = $event->_alias;
// etc.
}
I tried the event's subject() method which extracts the table object from the event,
$table = $event->subject();
When I var_dump or debug the object returned, it shows:
Debug: App\Model\Table\CompaniesTable Object
(
[registryAlias] => Companies
[table] => companies
[alias] => Companies
[entityClass] => App\Model\Entity\Company
[associations] => Array
(
[0] => defaultshippingusers
(...)
[defaultConnection] => default
[connectionName] => default
)
But I cannot access 'table', 'alias', etc. from my $table object.
When I do, I get a fatal error:
Table Companies is not associated with 'alias'
Is there a simple way to get the Table name from the $event object in a behavior?
Dumping objects does not necessarily give you a representation of the objects structure, but custom formatted debug information, defined via the magic __debugInfo() method.
https://github.com/cakephp/cakephp/blob/3.0.11/src/ORM/Table.php#L2190
Table classes do not have table or alias properties, but methods with the same name, just have a look at the Cookbook and the API docs.
$alias = $event->subject()->alias();
Cookbook > Database Access & ORM > Table Objects > Basic Usage
API > \Cake\ORM\Table
API > \Cake\ORM\Table::alias()
API > \Cake\ORM\Table::table()
...
You can also get the table name or alias directly from the behaviour without using the $event object:
$this->getTable()->table();
$this->getTable()->alias();
This is useful if you add your own functions to the behaviour which don't pass in the $event.

How to do Kohana Validation of $_serialize_column inside ORM

The validation on Kohana ORM is done using rules
function rules()
{
return array(
'username' => array(
array('not_empty'),
array(array($this, 'availability')),
)
);
}
I'm struggling to validate a JSON encoded column using $_serialize_columns.
class Model_Admin extends ORM {
protected $_belongs_to = array();
protected $_has_many = array(
'plans' => array(),
'groups' => array(),
'transactions' => array(),
'logins' => array()
);
protected $_serialize_columns = array('data');
/**
* #param array $data
* #param Validation $validation
*
* #return bool
*/
public function data($data, $validation)
{
return
Validation::factory(json_decode($data, TRUE))
// ... rules ...
->check();
}
public function rules()
{
return array(
'data' => array(
array(array($this, 'data'), array(':value',':validation')
)
);
}
}
the array that gets encoded is:
array(
'name' => '',
'address' => '',
'phone' => '',
'postalcode' => ''
);
the data method receives the json encoded data, because the ORM runs the filters before doing the validation, so I need to convert it back to an associative array, then create a new validation object to check specifically for the content of that array. Because I can't merge Validation rules from another Validation instance
Updated Answer
The use of a second validation object is necessary since save() causes the internal model validation object to be checked. This means that rules added to the validation object being checked from a validation rule will be ignored (Validation->check() imports the rules into local scope before looping).
Since the data itself is technically another object (in the sense of object relationships, it has its own dataset that needs validation) the ideal solution would be to find a way to create a real model that saves the data.
There are numerous other benefits to saving data with proper database column definitions, not least if you need to perform data property lookups, make in-situ changes etc. (which would otherwise require unserializing the data column, potetnailly in all rows).
There are some alternatives, but they feel like kludges to me:
Create a model that represents the data object and add rules to it, using check() to validate the data (problem: will require a lot of maintenance, no real-world table means columns must be manually defined).
Set the data as real columns in the Admin model, and use a filter that will convert it into the data column on set (problem: again, must manually define the columns and exclude the additional columns from the save operation).
I hope this is of some use.
Original Answer
The Kohana ORM save() method permits the inclusion of an "extra" validation object, which is merged into the main ORM validation object namespace.
This is documented briefly here.
If I have understood correctly, I think you are looking to do something like this:
// another script, e.g., a controller
// Create the model
$admin = ORM::factory('Admin');
// $data = the data as an array, before serialization ...
$extra_validation = Validation::factory($data)
// add ->rule() calls here, but DO NOT chain ->check()
;
// Set $data in the model if it is going to be saved, e.g., $admin->data = $data;
// Set other data... e.g., $admin->foo = 'bar';
// Save the model
try {
$admin->save($extra_validation);
}
catch (ORM_Validation_Exception $e)
{
// Manipulate the exception result
}
While in this example you must still create another validation object, you are now able to catch all exceptions in a single block. I would recommend using var_dump() or similar on $e->errors() to check the namespace if you are using i18n messages to provide a human-readable error message. You should find that a namespace called "_external" has been created in the response.