Cake PHP Translate Behavior, multiple field anomaly? - cakephp

I use the core translator of CakePHP and i have an anomaly.
I have an application with 90 tables. 80 tables have at least on field to translate. Some tables have more than one field.
All tables have just few records (i'm in development step)
I have no problem in my application with all tables with only one field to translate but when i use tables with more than one field to translate, it seems that i have an infinite loop with at then end a problem of memory.
Exemple of a code
MarketsTable.php
class MarketsTable extends BaseTable {
public function initialize(array $config){
parent::initialize($config);
$this->setTable('markets');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Translate', ['fields' => ['name_lang','description_lang']]);
}
public function buildRules(RulesChecker $rules) {
$rules->add($rules->isUnique(['name_lang']));
$rules->add($rules->existsIn(['language_id'], 'Languages'));
return $rules;
}
}
Market.php
class Market extends EntityBase{
use LazyLoadEntityTrait;
use TranslateTrait;
protected $_accessible = ['*' => true,'id' => false];
}
And finally i have a little thing very strange, but i imagine some specialist of PHP will explain me it. I found a "solution" to solve my problem.
If i a line of code in the class TranslateBehavior, the problem doesn't exist any more (the code start at the line 207 of TranslateBehavior.php)
public function beforeFind(Event $event, Query $query, $options){
$locale = $this->locale();
if ($locale === $this->getConfig('defaultLocale')) {
return;
}
$conditions = function ($field, $locale, $query, $select) {
return function ($q) use ($field, $locale, $query, $select) {
$q->where([$q->repository()->aliasField('locale') => $locale]);
if ($query->isAutoFieldsEnabled() || in_array($field, $select, true) || in_array($this->_table->aliasField($field), $select, true)) {
$q->select(['id', 'content']);
}
// line added by myself
$q->__toString();
return $q;
};
};
// ...
}
How this line "$q->__toString();"can solve my problem ???? :s

Related

How to properly insert time when user leaves( user_left and user_joined got the same value)

In this code, I would like to get time when the user joined and left and store it to DB. What happens it that I get the same value in both 'joined' and 'left' tables. How to fix it so it would store different values?
Schema::create('user_info', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('ip');
$table->string('joined');
$table->string('left');
});
in LoginController
public function logout() {
$left = now();
auth()->logout();
session()->forget('name');
session()->put('left', $left);
return redirect('/');
}
in Model
protected $fillable = ['ip','name', 'joined'];
const CREATED_AT = 'joined';
const UPDATED_AT = 'left';
public static function storeUser() {
UserInfo::create([
'ip' => Request::ip(),
'name' => Auth::user()->name,
'joined' => now(),
]);
}
BroadcastServiceProvider.php
Broadcast::channel('chat', function ($user) {
$ip = Request::ip();
$time = now();
if (auth()->check() && !session()->has('name')) {
UserInfo::storeUser();
session()->put('name',$user->name);
return [
'id' => $user->id,
'ip' => $ip,
'name' => $user->name,
'joined' => $time,
];
}
});
This image illustates the behaviour after some changes you'll see below. It show that data with key 'left' for now goes not to the intended user but to the first user with this name.
The follow up of this question is here How to override this code so that it insert data properly?
CREATED_AT and UPDATED_AT are timestamps that gets changed by the Eloquent model, whenever a model gets created it's also modified or updated from a non-existing to existing so this is why you get the same value
In the logout function, update the user's left column
public function logout() {
$user_id = auth()->id(); // Get authenticated user ID
$user_info = App\UserInfo::find($user_id); // Get user info
$user_info->left = now(); // Change here
$user_info->save(); // Update here
auth()->logout();
session()->forget('name');
session()->put('left', $left);
return redirect('/');
}
According to your table, there's no way to distinguish between users and their info since the name is not unique
Make a user_id based relationship
User model
public function info()
{
return $this->hasOne(UserInfo::class);
}
UserInfo model
public function user()
{
return $this->belongsTo(User::class);
}
And in the user_infos migration
Schema::create('user_infos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->string('name');
$table->string('ip');
$table->dateTime('joined');
$table->dateTime('left');
});
Cleaner Method
public function logout() {
$info = auth()->user()->info; // Get user info
$info->left = now(); // Change here
$info->save(); // Update here
auth()->logout();
session()->forget('name');
session()->put('left', $left);
return redirect('/');
}
Hope this helps

How to insert into a table based on an Eloquent relationship an array of foreign keys

I have two models TeamMember and ProjectRequest.
A TeamMember can have one ProjectRequest, that is why I created the following Eloquent relationship on TeamMember:
class TeamMember extends Model {
//
protected $table = 'team_members';
protected $fillable = ['project_request_id'];
// Relations
public function projectTeam() {
return $this->hasOne('\App\Models\ProjectRequest', 'project_request_id');
}
}
In my Controller I want to query both tables, however it returns the failure message.
What is important to know is that $request->projectTeam is an array of emails, looking like this:
array:2 [
0 => "mv#something.com"
1 => "as#something.com"
]
Meaning that I need to bulk insert into team_members table the project_request_ id for each team member where the emails are in the array.
How can I do that in the right way? The following is my attempt:
public function createProjectTeam(Request $request){
try {
$title = $request->projectTitle;
$TeamMember = $request->projectTeam;
$projectRequest = ProjectRequest::create(['project_title' => $title]);
$projectRequestId = $projectRequest->id;
$projectTeam = $this->teamMembers->projectTeam()->create(['project_request_id'=> $projectRequestId])->where('email', $TeamMember);
//$projectTeam = TeamMember::createMany(['project_request_id' => $projectRequestId])->where($TeamMember);
//dd($projectTeam);
return $projectRequest.$projectTeam;
} catch(\Exception $e){
return ['success' => false, 'message' => 'project team creation failed'];
}
}
There are a few things you can do.
Eloquent offers a whereIn() method which allows you to query where a field equals one or more in a specified array.
Secondly, you can use the update() method to update all qualifying team members with the project_request_id:
public function createProjectTeam(Request $request)
{
try {
$projectRequest = ProjectRequest::create(['project_title' => $request->projectTitle]);
TeamMember::whereIn('email', $request->projectTeam)
->update([
'project_request_id' => $projectRequest->id
]);
return [
'success' => true,
'team_members' => $request->projectTeam
];
} catch(\Exception $e) {
return [
'success' => false,
'message' => 'project team creation failed'
];
}
}
I hope this helps.

Codeigniter autocheck db depending on session value

I'm trying to force my app to check every time it loads a model or controller depending on which is my session value.
This is actually running, but just when I get throw this model.
class News_model extends CI_Model {
public function __construct()
{
parent::__construct();
if($this->session->dbname=='db1'){
$this->db=$this->load->database('db1', TRUE);
}
else{
$this->db=$this->load->database('db2', TRUE);
}
}
public function get_news($slug = FALSE)
{
if ($slug === FALSE)
{
$query = $this->db->get('news');
return $query->result_array();
}
$query = $this->db->get_where('news', array('slug' => $slug));
return $query->row_array();
}
}
But I do not war to include that __construct code to all my models or controllers.
I've tried to add on my autoload.php
$autoload['model'] = array('General');
Where my General code is something like this.
class General extends CI_Model {
function __construct()
{
parent::__construct();
if($this->session->dbname=='db1'){
$this->db=$this->load->database('db1', TRUE);
}
else{
$this->db=$this->load->database('db2', TRUE);
}
}
}
How can I do it?
You can do it by creating a base model which will be extended by your models that require the database check.
I have simplified the checking and loading code. A simple ternary determines the string to use and stores it in the variable $dbname. That variable is used to load the database, i.e. $this->load->database($dbname);.
I don't believe you need the second argument to load::database() which means you don't need to set $this->db explicitly. If I'm wrong, use
$this->db = $this->load->database($dbname, TRUE);
Below is the "base" model. The prefix of the file name is determined in config.php with the setting $config['subclass_prefix'] = 'MY_'; Adjust your base model's file and class name to match the 'subclass_prefix' you use.
/application/core/MY_Model.php
<?php
class MY_Model extends CI_Model
{
public function __construct()
{
parent::__construct();
$dbname = $this->session->dbname == 'db1' ? 'db1' : 'db2';
$this->load->database($dbname);
}
}
Use the above to create other models like so...
class News_model extends MY_Model
{
public function get_news($slug = FALSE)
{
if ($slug === FALSE)
{
$query = $this->db->get('news');
return $query->result_array();
}
$query = $this->db->get_where('news', array('slug' => $slug));
return $query->row_array();
}
}

Symfony3 return array from query to json

I have problem, i can't return my posts array to json becouse symfony returns array with entity object?
Its my code:
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('AppBundle:Post')->findAll();
return $this->json($posts);
}
I use $this->json is return json data, feature added on sf3.
But this is my result:
[
{},
{},
{}
]
i want to load my posts.
ps. i know, i can use Query builder, and method toArray or something, but is any method to use and DRY? Thx
Because entity can have multiple boundaries, proxy objects and related entities, I personally prefer to explicitly specify what is about to be serialized, like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
/** #Column(length=50) */
private $title;
/** #Column(length=50) */
private $text;
public function jsonSerialize()
{
return array(
'title' => $this->title,
'text' => $this->text,
);
}
}
And then it's as simple as json_encode($someEntityInstance);.
You can use JMSSerializerBundle as well to accomplish your task DRY.
Also, there is an option to write your own serializer to normalize the data.
UPDATE:
If you want multiple representations of a JSON, it can be achieved like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
// ...
protected $isList;
public function toList()
{
$this->isList = TRUE;
return $this;
}
private function jsonSerializeToList()
{
return [ // array representing list... ]
}
public function jsonSerialize()
{
if( $this->isList ) {
$normalized = $this->jsonSerializeToList();
} else {
$normalized = array(
'title' => $this->title,
'text' => $this->text,
);
}
return $normalized;
}
}
And called as json_encode($someEntityInstance->toList());. Any way, this is a bit dirty, so I suggest to be consistent with an idea of the interface.
A best solution is to enable the serializer component in Symfony:
#app/config/config.yml
framework:
serializer: ~
Note: the serializer component is disabled by default, you have to uncomment the config line in app/config/config.yml file.

CakePHP pass a array to paginate() - new model

Hi I've done a find() and added a new field to some of the results:
$approved = $this->ExpenseClaim->find('all', array('conditions'=> array('ExpenseClaim.claim_status_id' => '3')));
$i = 0;
foreach ($approved as $ap) {
$approved[$i]['ExpenseClaim']['claimTotal'] = $this->ExpenseClaim->expenseClaimTotal($approved[$i]['ExpenseClaim']['id']);
$i++;
}
I now need to pass this to paginate, however I read here that you cannot do this and that I must create another model to use the afterFind() method only on this one particular find.
So I've created the new Model called ExpenseClaimTotal and set the UseTable to
public $useTable = 'expense_claims';
Then in the new models afterFind() method I did a simple debug:
public function afterFind($results, $primary = false) {
debug($results);
//return $results;
}
But when I now try and do a find against this new model in pagesController it fails:
$this->loadModel('ExpenseClaimTotal');
$approved = $this->ExpenseClaimTotal->find('all', array('conditions'=> array('ExpenseClaim.claim_status_id' => '3')));
This is the error I get:
Database Error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'ExpenseClaim.claim_status_id' in 'where clause'
SQL Query: SELECT `ExpenseClaimTotal`.`id`, `ExpenseClaimTotal`.`user_id`, `ExpenseClaimTotal`.`claim_status_id`, `ExpenseClaimTotal`.`created`, `ExpenseClaimTotal`.`modified`, `ExpenseClaimTotal`.`approved`, `ExpenseClaimTotal`.`approved_by`, `ExpenseClaimTotal`.`declined_by`, `ExpenseClaimTotal`.`date_submitted` FROM `expenses`.`expense_claims` AS `ExpenseClaimTotal` WHERE `ExpenseClaim`.`claim_status_id` = 3
There doesnt seem to be much in the docs about using 2 models for one table
You don't want to paginate an array
You're already performing a find, it's not sensible to perform a find and then paginate the resultant array.
Simply paginate your model data directly and inject your total values in the process. As such - if you put your original "added a new field to some of the results" logic in the model:
class ExpenseClaim extends AppModel {
public function afterFind($results, $primary = false) {
foreach ($results as &$ap) {
if (isset($ap['ExpenseClaim']['id'])) {
$ap['ExpenseClaim']['claimTotal'] = $this->expenseClaimTotal($ap['ExpenseClaim']['id']);
}
}
return $results;
}
}
Your controller code becomes simply:
public function index() {
$conditions = array('ExpenseClaim.claim_status_id' => '3');
$data = $this->paginate($conditions);
$this->set('data', $data);
}
And the code is simple and "just works".
Enhancements
The above is the simplest way to achieve the desired results, but has some disadvantages - namely it will call the total method on pretty much all finds.
Depending on exactly what you're doing you may wish to for example:
Cache your totals
If appropriate, you can remove problems by simply adding the field "claim_total" to the database, and recalculate whenever it changes. That would mean there is absolutely no extra logic when reading from the expense claim model.
Use a custom find type
If you don't want to recaculate the total on all finds - you can create a custom find type
class ExpenseClaim extends AppModel {
public $findMethods = array('allWithTotals' => true);
protected function _findAllWithTotals($state, $query, $results = array()) {
if ($state === 'before') {
return $query;
}
foreach ($results as &$ap) {
$ap['ExpenseClaim']['claimTotal'] = $this->expenseClaimTotal($ap['ExpenseClaim']['id']);
}
return $results;
}
And then use it in your paginate call:
public function index() {
$this->paginate['findType'] = 'allWithTotals'; # <-
$conditions = array('ExpenseClaim.claim_status_id' => '3');
$data = $this->paginate($conditions);
$this->set('data', $data);
}
In this way, only the index method will trigger the call to add the totals.

Resources