I have a database column called modified with ON UPDATE CURRENT_TIMESTAMP definition.
When I modify and persist existing object the column stays with old value, because it is already set in the object property. Is there a way to tell Doctrine not to set that object property when persisting?
I got the desired result using unset before persisting, but this will make the code messy as not all entities have that property.
unset($object->modified);
$entityManager->persist($object);
Solved it by adding a LifecycleEvent.
In my ClassMetadataBuilder I have a method for creating the field:
public function addModifiedTimeField(): void {
$this->createField("modified", "timestamp")->build();
$this->addLifecycleEvent("unsetModified", "preFlush");
}
And entities that require modified field extend a Versionable class that defines the method.
abstract class Versionable extends JsonEncodable {
protected $modified;
public function getModified() {
return $this->modified;
}
public function unsetModified(): void {
$this->modified = null;
}
}
Related
I've been reading this post : Doctrine - how to check if a collection contains an entity
But I actually don't like the solution, as, doctrine already provide the contains() method, which have the advantage to keep logic directly into the object, and then to not load EXTRA_LAZY collections entirely.
So here a Cart Entity own a CartProduct collection as is :
/**
* ...
* #ORM\Entity(repositoryClass="App\Repository\CartRepository")
*/
abstract class Cart implements InheritanceInterface{
...
/**
* #ORM\OneToMany(targetEntity="CartProduct", mappedBy="cart", fetch="EXTRA_LAZY", cascade={"persist"})
*/
private Collection $cartProducts;
...
public function __construct()
{
$this->cartProducts = new ArrayCollection();
}
...
}
(CartProduct have to be an Entity look at this simplify EA model. That's a standard way to proceed for related entity holding extra fields)
Now I want to add a new ProductCart Entity to my Cart class.
So I'm adding this method (generated by Symfony make:entity) :
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->contains($cartProduct)) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
And then I test this code :
public function testAddCartProduct()
{
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
}
But when I run this test, it fail :
Failed asserting that actual size 2 matches expected size 1.
So I check, and the Cart.cartProducts Collection have two product which are exactly the same objects.
As it's an ArrayCollection, I suppose that it just use this method :
namespace Doctrine\Common\Collections;
class ArrayCollection implements Collection, Selectable {
...
public function contains($element)
{
return in_array($element, $this->elements, true);
}
So well, of course in this case it is just return false, And the objects are considered to be different.
So now, I wish I could use PersistentCollection instead of ArrayCollection when implementing the Collection object , because the PersistentCollection.contains() method looks better.
abstract class Cart implements InheritanceInterface{
...
public function __construct()
{
-- $this->cartProducts = new ArrayCollection();
++ $this->cartProducts = new PersistentCollection(...);
}
}
But this require an EntityManager as a parameter, so, seams a little bit overkill to give an EntityManager to an Entity object...
So I finally, I don't know what is the better way to check for a dupplicate entity inside a collection.
Of course, I could implement myself a thing like :
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->filter(
function (CartProduct $cp)use($cartProduct){
return $cp->getId() === $cartProduct->getId();
})->count()) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
But it'll require to load every Entity and I really don't like the idea.
Personally I agree with your comment, I don't think the entity itself should have the responsibility to ensure there is no duplicate.
The entity cannot make a request like a repository could, and I don't see how you can be sure there is no duplicate in the database without querying it.
Calling contains will not trigger a fetch in your case, this means the collection will stay as is, which is not what you want anyway because you could have a previously persisted duplicate that will not be part of the collection because you marked it as EXTRA_LAZY.
You also don't want to fetch all the entities of the collection (and transform the results into objects) just to check if you have a collision.
So IMHO you should create a method in the repository of the entity to check for duplicates, a simple SELECT COUNT(id).
Then there is your real problem.
The way you make your test will never find a collision. When you do:
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
You are creating two instances of CartProduct, that's why the call to contains doesn't find anything.
Because contains checks for the object reference, not the content, like you can see in its implementation:
public function contains($element)
{
return in_array($element, $this->elements, true);
}
So in your test case what you're really testing is:
in_array(new CartProduct(), [new CartProduct()], true);
which will always return false.
All my classes that connect to a database need to get values of custom columns from their respective tables. So instead of coding a function for each class, is there a way for me to implement a base class from which my classes extend and I can use that base class function to easily get and update data on my database (at least for simple data).
class Users extend BaseClass
{
private $table = "users";
private $columns = ["name", "email", "password"];
}
so from an outside function, I can access the email value like this
Users->where("name", "John")->getEmail();
or possibly
Users->where("name", "John")->get("email");
I could also use this method to update data to the database. The functions where should be universal so it should exist in BaseClass. (I know the database queries that I should use, what I want to know is how to call get after calling where and also possibly setting multiple where requirements).
Users->where("name", "John")->where("last_name", "Smith")->get("email");
I think you want something like this
abstract class BaseClass
{
private $where_clauses=[];
private $columns=[];
private $table='';
protected function setData($table,$cols){
$this->columns=$cols;
$this->table=$table;
}
public function where($key, $value){
$this->where_clauses[$key]=$value;
return $this;
}
public function get($col){
$sql='SELECT '.$col.' FROM '.$this->table.' WHERE';
$first=true;
foreach($this->where_clauses AS $key=>$val){
if(!$first) sql.=' AND ';
$first=false;
$sql.=$key.' = '.$val;
}
// RUN QUERY, Return result
}
}
Note that the where function returns a reference to $this, which is what let's you string the function calls together (not tested the code). This would also need some adapting to let you put two conditions on the same column.
Is there any reason why we only have this static way to boot traits in Laravel:
static function bootMyTrait ()
{...}
Is there any way to boot trait and have model instance in the boot function? Like this:
function bootMyTrait ()
{
if ($this instanceOf awesomeInterface)
{
$this->append('nice_attribute');
}
}
I need this AF, and for a very long time haven't found any solution.
Since Laravel 5.7 you can use trait initializers, instead of trait booters. I've had the same task and was able to solve it like this:
public function initializeMyTrait()
{
if ($this instanceOf awesomeInterface)
{
$this->append('nice_attribute');
}
}
Well, no one seems to care :D
Good news, is that within 15 min, I've solved my problem with this in base model:
public function __construct(array $attributes = [])
{
foreach (class_uses_recursive($this) as $trait)
{
if (method_exists($this, $method = 'init'.class_basename($trait))) {
$this->{$method}();
}
}
parent::__construct($attributes);
}
Edit
Instead of relying on traits for this, use Eloquent's accessors and mutators. For example, define the following methods on a User model:
// Any time `$user->first_name` is accessed, it will automatically Uppercase the first letter of $value
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
This appends the $user->first_name attribute to the model. By prefixing the method name with get and suffixing it with Attribute you are telling Eloquent, Hey, this is an actual attribute on my model. It doesn't need to exist on the table.
On the other hand you can define a mutator:
// Any string set as first_name will automatically Uppercase words.
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = ucwords($value);
}
This will apply anything you do to $value before setting it in the $attributes array.
Of course, you can apply these to attributes that do exist on your database table. If you have raw, unformatted data, say a telephone number 1234567890, and you wanted to apply a country code, you could use an accessor method to mask the number without modifying the raw value from the database. And going the other way, if you wanted to apply a standard formatting to a value, you could use a mutator method so all your database values conform to a common standard.
Laravel Accessor and Mutators
I'm currently working on a PHP script, and I'm not sure what is the best practise for the following:
Since PHP does not allow multiple constructors within a class (User), I have a constructor which has an array as argument (for the id,name,address, see code block below).
When this array does not contain an id, but is does contain other attributes (such as name, address), it calls the insert method of the class and assigns the id of the inserted row to the id attribute of the class.
When an array which does contain an id, name and address is passed to the constructor, it assigns these values to the corresponding attributes.
When I want to edit the user in the database, I can implement it in the following ways:
Add an option to the constructor to only pass the id in the array
(without name and address attributes, because these are not known
when calling the update method), and than call update method on this
instance. A problem with this is that you've got an instance which is
not a good representation of the real object (as in the database).
For example, you can't call the getName() method since name is not
set.
Same as above, but now when only the id is passed to the constructor,
load the other attributes from the database, so that the instance is
a correct representation of the object as in the database. Then the
update method can be called on this instance.
Make the update method static, so that I can call the update method
without making an instance
The problem with the first two points is, that I get kind of spaghetti code in my constructor since I have to check which attributes are passed to the array in the constructor.
Which solution do you think I can choose best?
Do you've got a better approach which will not result in large amounts of spaghetti code in the constructor?
<?php
class User
{
private $id;
private $name;
private $address;
public function __construct($data)
{
$this->name = $data['name'];
$this->address = $data['address'];
if (isset($data['id'])) {
$this->id = $data['id'];
} else {
$result = $this->insert();
$this->id = $wpdb->insert_id; //Wordpress method for retrieving insert id
}
}
public function get_id() { return $this->id; }
public function get_name() { return $this->name; }
public function get_address() { return $this->address; }
public function insert() { //insert into DB }
public function update($data) { //update in DB the $data attributes}
public function delete() { //delete from database }
public static function get_user_list()
{
// load users from the database
// foreach user {
// create User instance by passing the id, name and address values from the database in an array to the constructor of User
// add user instance to a result array
// }
// return result array
}
}
?>
Create a method called loadUser (or something you prefer), if the data array passed to the constructor has and ID, use the loadUser method to load the details. If not, you can insert a user. After this you can move onto editing the user.
Is it possible to override values from Model->fetchAll() so it work globally. I have tried to override this in model, but does not work:
class Application_Model_DbTable_OdbcPush extends Zend_Db_Table_Abstract
{
public function __get(string $col)
{
$res = parent::__get($col);
if ($col == "lastrun") {
$res = ($res == "1912-12-12 00:00:00+07" ? NULL : $res);
}
return $res;
}
//...
}
In a controller:
$odbcModel = new Application_Model_DbTable_OdbcPush();
$rs = $odbcModel->fetchAll( $select );
I want to override value returned from fetchAll(), find() etc when col name is "lastrun";
The way you're going about this isn't going to work. __get is used to get data from protected or private properties and typically used in conjunction with getters.
For example, if you implemented __get() in your Application_Model_DbTable_OdbcPush class you could do something like:
$model = new Application_Model_DbTable_OdbcPush();
//echo out the _primary property (primary key of the table)
echo $model->primary;
and expect it to work. Because _primary exists as a property in Zend_Db_Table_Abstract.
To do what you want to do you'll need to do it after the result set has been returned (unless you want to rewrite the whole Zend Db component). Just run the result set through a foreach and change the value of lastrun to whatever you want.
I tried to find a place to override the Zend Db components to do what you want, but it would involve to many classes.
Remember that when using DbTable classes, they only interact with one table. You'll need to duplicate code for every table you want to effect or you'll need to extend a base class of some kind.
You always have the option to use straight Sql to frame whatever query you can come up with.
Good Luck!
Found the answer, for community i share here :D
http://framework.zend.com/manual/1.12/en/zend.db.table.row.html
So we have to overload Zend_Db_Table_Row and assign it to model/dbtable:
class Application_Model_DbTable_Row_OdbcPush extends Zend_Db_Table_Row_Abstract
{
// do some override here
}
class Application_Model_DbTable_OdbcPush extends Zend_Db_Table_Abstract
{
protected $_name = 'odbcpush';
protected $_primary = 'id';
private $_global = null;
protected $_rowClass = "Application_Model_DbTable_Row_OdbcPush";
// etc
}