Symfony - clone all values from one object onto another by ID? - php

I am stuck in a huge mess.
I am trying to write a method that should receive user_id and check if there is a profile change request attached to that user.
If there is (has field change_request set to true in users table), the change request should be applied -> all user data fields from change request table by that id should be moved to that user table.
My service
public function getUserApplyChangeRequest($id)
{
$a =$this->getUserRepository()->find($id);
$b =$this->getChangeProfileRequestRepository()->find($id);
$b = clone $a;
$this->em->persist($b);
$this->em->flush();
}
My controller..
public function userApplyChangeRequestAction($changeRequest)
{
$this->requirePostParams(['user_id']);
if ($changeRequest === 1){
$applyChange = $this->get('user')->getUserApplyChangeRequest($this->getUser());
}
return $this->success();
}
I need help because I am stuck and don't really know what to do wtih this lines of code but I putted an example of what I want to happen.

Easiest would be to set the properties yourself if there are only five:
public function getUserApplyChangeRequest($id)
{
$a =$this->getUserRepository()->find($id);
$b =$this->getChangeProfileRequestRepository()->find($id);
$a->setPropertyOne($b->getPropertyOne());
$a->setPropertyTwp($b->getPropertyTwo());
$this->em->persist($a);
$this->em->flush();
}
Other option is to get all the properties of your Change Object with doctrine and call the getters/setters that way (untested, make sure to add NULL checks and skip the ID field):
$props = $em->getClassMetadata(get_class($b))->getColumnNames();
foreach($props as $prop){
//get value from B
$reflectionMethod = new ReflectionMethod(get_class($b),'get'.ucfirst($prop));
$value = $reflectionMethod->invoke($b);
//set value in A
$reflectionMethod = new ReflectionMethod(get_class($a),'set'.ucfirst($prop));
$reflectionMethod->invoke($a, $value);
}

Related

How to Select Fields When Refreshing Model in Laravel

As you probably know, we can use refresh method on a model instance in order to refresh it from the database.
Refresh completely reloads all columns from database. refresh method does not seem to take any input, so I was wondering if there is a way to refresh some fields from the database.
To demonstrate, let's have a pseudo-model as below:
Foo
---
bar: char
baz: char
And I have a single instance of Foo called $foo. $foo->refresh() fetches both $foo->bar and $foo->baz. How do I only fetch/refresh bar field?
Thanks in advance.
Environment
PHP 7.4.5
Laravel 7.x
I think there's no way to do such. The best option you have is to get the PK of your $foo object and query for the fields you want:
$id = $foo->id;
$refreshed = Foo::select('bar')->where('id', '=', $id)->first();
$foo->bar = $refreshed->bar;
However, this will unsync your model with the database. Another option is to create a custom method in your model that does it in a more abstract way and retrieves the new unsynced object:
// In your model:
public function refreshField(string $field)
{
$this->$field = (new static())
->select($field)
->where($this->key(), '=', $this->{$this->key()})
->first()->$field;
return $this;
}
// Then outside:
$foo = Foo::first();
// perform actions. . .
$foo->refreshField('bar');
Again, keep in mind that this will keep your model unsynced with the database. Also, this is a POC, I didn't test it, but the general idea should work. If you create a parent model and extend all your other models from this (which extends from Eloquent), this should work with any of your models.
For a more complete version, you can pass an array instead of a string and retrieve all the fields that you want to refresh, then assign them in a loop:
public function refresFields(string ...$fields)
{
$fields = $this->$field = (new static())
->select($fields)
->where($this->key(), '=', $this->{$this->key()})
->first();
foreach ($fields as $field) {
$this->$field = $field;
}
return $this;
}
I wouldn't recommend this, but if you really need it, give it a try. Good luck

How to edit a property of another class and return it to another class?

So my problem is : I'm creating a management system with php, html5, css etc(school project).
When a user logs in I save its data in a class. The whole purpose of saving them is to later on use them when a purchase is done, so I can save the product ID and the user ID. But whenever I do the query to the DB I get an Undefined variable error.
This is my class and its methods in it. One is used to save data and one to return them.
class profile_attributes{
public $u_data;
function attributes($u_data){
$this->u_data=$u_data;
}
function attr_get(){
return $u_data;
}
}
How I initially send arguments
$u_data = mysqli_fetch_assoc($result);
$save_info = new profile_attributes()->attributes($u_data);
How I try to get them
$profile = new profile_attributes();
$loged_user = $profile->attr_get();
$user_id = $loged_user['id'];
It doesn't look like you're ever defining $u_data. All you're doing is creating a new instance of profile_attributes and then trying to pull the undefined $u_data.
You are creating two separate instances of your profile_attributes class. An instance's properties and data are specific to itself, and do not get shared if you create a second instance using the new class syntax.
Here's an example of what you're doing. Notice that the data inside the instances are not the same:
class Foo
{
public $data;
}
$instance1 = new Foo();
$instance1->data = array[1, 2, 3];
$instance2 = new Foo();
var_dump($instance1, $instance2);
You need to share the first instance you create and save the database result to the place where you want to retrieve those attributes.
$u_data is not defined, you must precede with $this
class profile_attributes{
public $u_data;
function attributes($u_data){
$this->u_data=$u_data;
}
function attr_get(){
return $this->u_data;
}
}
You were creating an instance to set the attributes and another to access them, use just one for both:
// Use the same instance to set and get atributes
$profile = new profile_attributes();
$u_data = mysqli_fetch_assoc($result);
$save_info = $profile->attributes($u_data);
$loged_user = $profile->attr_get();
$user_id = $loged_user['id'];

Php Classes Unique Data Member

I'm really new in PHP, our instructor just teaching us C++ OOP and I want to try it on PHP.
I'm creating objects with my class.
class TwitterUser {
private $twittername;
public function TwitterUser($a)
{
$this->twittername = $a;
// echo $this->twittername;
}
}
$reader = new Spreadsheet_Excel_Reader($target_path);
$veriler = $reader->sheets[0]['cells'];
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter")
{
$kisiler[] = new TwitterUser(temizle($veri[$sutun]));
}
}
What I want is, if one object has same string with other object in $twittername data member, don't create new object.
This task is usually done using some kind of Model -> database approach (such as Doctrine), in which case you save the model data into database. The database table should be designed to not allow the same name for more than one record and the logic to enforce and error handle this can be built into the model class.
You can achieve the same by pure PHP, but it requires existing instances to be stored somehow so when creating new instances, existing ones can be checked for uniqueness.
You don't want to add the object if the username is test? Basically you can't back out of a constructor. Just add a simple flag to only add "test" user once.
Using your code sample:
$testuserexists = false;
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter" && $testuser == false)
{
$kisiler[] = new TwitterUser(temizle($veri[$sutun]));
if ($veri[$sutun] == "Test")
$testuserexists = true;
}
}
Or if you are trying to not have duplicates:
foreach($veriler as $veri)
{
if(!empty($veri[$sutun]) and $veri[$sutun]!="Twitter" && !isset($kisiler[$veri[$sutun]]))
{
$kisiler[$veri[$sutun]] = new TwitterUser(temizle($veri[$sutun]));
}
}
I don't know what the temizle function is supposed to do, but basically you can assign the username as the associative array key and prevent duplicates by adding an isset() to your conditional.

Yii composite primary keys with isNewRecord

I have a mysql table with composite keys ( user_id , category_id );
I am trying to update the last access for these records as following
$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
echo $userCategory->isNewRecord; //always true
$userCategory->last_access = Now();
$userCategory->save();
The {$userCategory->isNewRecord} and when I try to save() the MySQL generates a duplicate error for the composite Primary keys.
I also added this to UserCategory model but didn't help
public function primaryKey() {
return array('user_id', 'category_id');
}
****Update:
Sorry for the confusion. My question is how to achieve the same result as "ON DUPLICATE KEY UPDATE" in the Yii framework. In other words, how to do the insert or update in one SQL query. if you look at the source code for save()
public function save($runValidation=true,$attributes=null)
{
if(!$runValidation || $this->validate($attributes))
//checking if new record
return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);**
else
return false;
}
Actually, the problem is that if isNewRecord is always true, it means that Yii is going to use an INSERT statement instead of an UPDATE statement when saving the model to the database.. that is why you always get the duplicate pk error, even if it's composite.
Here is the official documentation about IsNewRecord . So, the problem is that you're using
$userCategory = new UserCategory; //Always a new record, tries to INSERT
So to resolve this you have to find the record and evaluate if it is found before saving it, instead. Documentation can also be read Here about the find() family of methods and their return value, the return values of the find() methods vary slightly on their nature:
find..() returns the record found or NULL if no record is found.
findAll..() returns an array containing all the records found or an empty array if no records are found.
You can use this return value to differentiate wether a primary key exists or not:
$userCategory = UserCategory::model()->findByAttributes(array('user_id '=>1,'category_id '=>15));
// if user does not exist, you need to create it
if ($userCategory == NULL) {
$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
}
echo $userCategory->isNewRecord; //you will see the difference if it does exist or not exist
$userCategory->last_access = Now();
$userCategory->save();
This will ensure that the framework uses the INSERT or UPDATE statement correctly, avoiding the duplicate PK error you're getting.
Edit: Enhanced the example code to properly populate the record when it's new.
In your model, add the following method:
/**
* Uses the primary keys set on a new record to either create or update
* a record with those keys to have the last_access value set to the same value
* as the current unsaved model.
*
* Returns the model with the updated last_access. Success can be checked by
* examining the isNewRecord property.
*
* IMPORTANT: This method does not modify the existing model.
**/
public function updateRecord(){
$model = self::model()->findByPk(array('user_id'=>$this->user_id,'category_id'=>$this->category_id));
//model is new, so create a copy with the keys set
if(null === $model){
//we don't use clone $this as it can leave off behaviors and events
$model = new self;
$model->user_id = $this->user_id;
$model->category_id = $this->category_id;
}
//At this point we have a model ready for saving,
//and don't care if it is new or not
$model->last_access = $this->last_access;
$model->save(false);
return $model;
}
The above is inspired by a more general method that I use a lot to do a create-or-find-if-already-exists process.
Use the following code to execute this.
$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
echo $userCategory->isNewRecord; //always true
$userCategory->last_access = Now();
$userCategory = $userCategory->updateRecord();
Note that only the last line is different from your code. The fact that the instance of the model declared with new UserCategory is not altered is intended behavior.
You can then verify in your code whether or not the model saved with the following:
if(!$userCategory->isNewRecord){
echo 'save succeeded';
}
else{
echo 'save failed';
}
If you're trying to update, you should load record, instead of creating a new one.
UserCategory::model()->findByPk(array('user_id'=> 1,'category_id '=> 15));
$userCategory->last_access = Now();
$userCategory->save();
in UserCategory.php
public function isNewRecord()
{
$result = $this->findByAttributes(array('user_id'=>$this->user_id,'category_id'=>$this->category_id));
if($result === NULL)
{
return true;
}
return false;
}
then in the controller
$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
echo $userCategory->isNewRecord();
----
Another option is to modify the model to change the condition on the save function then call the parent save function: (this code goes in the UserCategory model)
public function save($runValidation=true,$attributes=null) {
$exists = UserCategory::model()->findByAttributes(array('category_id'=>$this->category_id,'user_id'=>$this->user_id));
if($exists) {
$this->isNewRecord = false;
}
return parent::save($runValidation,$attributes);
}
I just did a test and it seems to work correctly. You should just be able to do this:
$userCategory = new UserCategory;
$userCategory->user_id = 1;
$userCategory->category_id = 15;
$userCategory->last_access = Now();
$userCategory->save();
Should insert or update based off of whether it finds the record, so you don't have to change any of your other code.

Custom is_unique_logical_key - validation or callback?

Can I please have a design suggestion for the following problem:
I am using Codeigniter/Grocery_CRUD.
My system is multi tenanted - different autonomous sites - within the same client. I have quite a few instances of tables that have unique logical keys. One such table structure is:
equip_items
id (pk)
equip_type_id (fk to equip_types)
site_id (fk to sites)
name
Where (equip_type_id, site_id, name) together are a unique key in my db.
The issues is that when using a grocery_CRUD form to add or edit a record that breaks this database rule - the add or edit fails (due to the constraints in the db) but I get no feedback.
I need a variation on the is_unique form_validation rule by which I can specify the field*s* that must be unique.
The issues:
How to specify the rule? set_rules() is for a given field and I have multiple fields that the rule will apply to. Does that mean I should abandon the Form_validation pattern? Or do I follow the 'matches' rule pattern and somehow point to the other fields?
Perhaps a callback function would be better but this would mean writing a custom function in each model where I have this problem at last count this is 9 tables. It seems far better to do this in one place (extending form_validation).
Am I missing something already in codeigniter or grocery_CRUD that has already solved this problem?
Any suggestion/advice you might have would be appreciated.
EDIT:
Actually it appears the solution Johnny provided does not quite hit the mark - it enforces each field in unique_fields() being independently unique - the same as setting is_unique() on each one. My problem is that in my scenario those fields are a composite unique key (but not the primary key). I don't know if it is significant but further to the original problem statement: 1) site_id is a 'hidden' field_type - I don't want my users concerned they are on a different site so I'm dealing with site_id behind the scenes. 2) Same deal with an equip_status_id attribute (not part of the unique key). And 3) I have set_relations() on all these foreign key attributes and grocery_CRUD kindly deals with nice drop downs for me.
EDIT 2
I have solved this using a callback.
UPDATE: This code is now part of grocery CRUD version >= 1.4 and you don't need to use an extension anymore. For more see the documentation for unique_fields
I will try to explain it as easy as I can:
1. First of all for those who have grocery CRUD lower or equal to 1.3.3 has to use this small change: https://github.com/scoumbourdis/grocery-crud/commit/96ddc991a6ae500ba62303a321be42d75fb82cb2
2. Second create a file named grocery_crud_extended.php at application/libraries
3. Copy the below code at your file application/libraries/grocery_crud_extended.php
<?php
class grocery_CRUD_extended extends grocery_CRUD
{
protected $_unique_fields = array();
public function unique_fields()
{
$args = func_get_args();
if(isset($args[0]) && is_array($args[0]))
{
$args = $args[0];
}
$this->_unique_fields = $args;
return $this;
}
protected function db_insert_validation()
{
$validation_result = (object)array('success'=>false);
$field_types = $this->get_field_types();
$unique_fields = $this->_unique_fields;
$add_fields = $this->get_add_fields();
if(!empty($unique_fields))
{
$form_validation = $this->form_validation();
foreach($add_fields as $add_field)
{
$field_name = $add_field->field_name;
if(in_array( $field_name, $unique_fields) )
{
$form_validation->set_rules( $field_name,
$field_types[$field_name]->display_as,
'is_unique['.$this->basic_db_table.'.'.$field_name.']');
}
}
if(!$form_validation->run())
{
$validation_result->error_message = $form_validation->error_string();
$validation_result->error_fields = $form_validation->_error_array;
return $validation_result;
}
}
return parent::db_insert_validation();
}
protected function db_update_validation()
{
$validation_result = (object)array('success'=>false);
$field_types = $this->get_field_types();
$unique_fields = $this->_unique_fields;
$add_fields = $this->get_add_fields();
if(!empty($unique_fields))
{
$form_validation = $this->form_validation();
$form_validation_check = false;
foreach($add_fields as $add_field)
{
$field_name = $add_field->field_name;
if(in_array( $field_name, $unique_fields) )
{
$state_info = $this->getStateInfo();
$primary_key = $this->get_primary_key();
$field_name_value = $_POST[$field_name];
$ci = &get_instance();
$previous_field_name_value =
$ci->db->where($primary_key,$state_info->primary_key)
->get($this->basic_db_table)->row()->$field_name;
if(!empty($previous_field_name_value) && $previous_field_name_value != $field_name_value) {
$form_validation->set_rules( $field_name,
$field_types[$field_name]->display_as,
'is_unique['.$this->basic_db_table.'.'.$field_name.']');
$form_validation_check = true;
}
}
}
if($form_validation_check && !$form_validation->run())
{
$validation_result->error_message = $form_validation->error_string();
$validation_result->error_fields = $form_validation->_error_array;
return $validation_result;
}
}
return parent::db_update_validation();
}
}
4. Now you will simply have to load the grocery_CRUD_extended like that:
$this->load->library('grocery_CRUD');
$this->load->library('grocery_CRUD_extended');
and then use the:
$crud = new grocery_CRUD_extended();
instead of:
$crud = new grocery_CRUD();
5. Now you can simply have the unique_fields that it works like this:
$crud->unique_fields('field_name1','field_name2','field_name3');
In your case:
$crud->unique_fields('equip_type_id','site_id');
Pretty easy right?
This is checking if the field is unique or not without actually change the core of grocery CRUD. You can simply use the grocery_CRUD_extended instead of grocery_CRUD and update grocery CRUD library as normal. As I am the author of the library I will try to include this to grocery CRUD version 1.4, so you will not have to use the grocery_CRUD_extended in the future.
I have done this using a callback:
$crud->set_rules('name','Name','callback_unique_equip_item_check['.$this->uri->segment(4).']');
function unique_equip_item_check($str, $edited_id)
{
$var = $this->Equip_Item_model->is_unique_except(
$edited_id,
$this->input->post('site_id'),
$this->input->post('equip_type_id'),
$this->input->post('name'));
if ($var == FALSE) {
$s = 'You already have an equipment item of this type with this name.';
$this->form_validation->set_message('unique_equip_item_check', $s);
return FALSE;
}
return TRUE;
}

Categories