Doctrine: Set model values as array - php

I've got an array of values I want to update my model with.
Doctrine_Access provides a function setArray which is nearly exactly what I need - except it cares about values that don't have fields in the model. I want those to be ignored.
A little example. Say we have a User table with the field username.
$user = new User();
$user->setArray(array('username'=>'xyz'))->save();
That would work!
$user = new User();
$user->setArray(array('username'=>'xyz','anotherKey'=>'anotherValue'))->save();
That doesn't. I want Doctrine to just ignore anotherKey, if there is no related field.
The intention is, that I don't want to filter my arrays before I update my model.
What is the cleanest and easiest way to get this done?

Doctrine_Record::fromArray() solves it.
Unfortunately it doesn't return the object, so it's useless for method chaining...

this is useful
add find method to model:
class Address extends Doctrine_Record {
public static function factory() {
return new Address();
}
public function findById($id) {
$findObject = Doctrine::getTable('Address')->findOneByid($id);
return $findObject;
}
....
and use it
$address = Address::factory()
->findById(13)->set('name', 'new data')->set('anotherfield','another data')->save();

Related

How should I check for dupplicate entity in an object collection?

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.

How to implement models factory in frameworks which using active records?

So, in my framework X, let it be Phalcon, I often create models objects.
Let's assume that all fields already validated. Questions related only about creation logic.
A simple example of creating Users object and save it to DB:
<?php
$user = new Users();
$user->setName($name);
$user->setLastName($lastname);
$user->setAge($age);
$user->create();
For simplicity, I show here only 3 fields to setup, in the real world they always more.
I have 3 questions:
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
Example:
<?php
$factory = new UsersFactory();
$factory->make($name, $lastname, $address, $phone, $status, $active);
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
3) And even more, what if i will need to create Users objects with relations, with other related objects?
Thank you for any suggestions.
Your question starts out simple and then builds with complexity. Reading your post it sounds like your concerned about the number of arguments you would have to pass to the method to build the object. This is a reasonable fear as you should try to avoid functions which take more than 2 or 3 args, and because sometimes you will need to pass the 1st 3rd and 5th arg but not the 2nd and 4th which just gets uncomfortable.
I would instead encourage you to look at the builder pattern.
In the end it will not be that much different than just using your User object directly however it will help you prevent having a User object in an invalid state ( required fields not set )
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
This is why I recommended the builder pattern. To avoid passing a large number of params to a single function. It also would allow you to validate state in the build method and handle or throw exceptions.
class UserBuilder {
protected $data = [];
public static function named($fname, $lname) {
$b = new static;
return $b
->withFirstName($fname)
->withLastName($lname);
}
public function withFirstName($fname) {
$this->data['first_name'] = $fname;
return $this;
}
public function withFirstName($lname) {
$this->data['last_name'] = $lname;
return $this;
}
public function withAge($age) {
$this->data['age'] = $age;
return $this;
}
public function build() {
$this->validate();
$d = $this->data;
$u = new User;
$u->setFirstName($d['first_name']);
$u->setLastName($d['last_name']);
$u->setAge($d['age']);
return $u;
}
protected function validate() {
$d = $this->data;
if (empty($d['age'])) {
throw new Exception('age is required');
}
}
}
then you just do..
$user = UserBuilder::named('John','Doe')->withAge(32);
now instead of the number of function arguments growing with each param, the number of methods grows.
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
no it should not insert. it should just help you build the object, not assume what your going to do with it. You may release that once you build it you will want to do something else with it before insert.
3) And even more, what if i will need to create Users objects with relations, with other related objects?
In Phalcon those relationships are part of the entity. You can see in their docs this example:
// Create an artist
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';
// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; // Assign the artist
$album->year = 2008;
// Save both records
$album->save();
So to relate this back to your user example, suppose you wanted to store address information on the user but the addresses are stored in a different table. The builder could expose methods to define the address and the build method would create both entities together and return the built User object which has a reference to the Address object inside it because of how Phalcon models work.
I don't think it's entirely necessary to use a builder or "pattern" to dynamically populate your model properties. Though it is subjective to what you're after.
You can populate models through the constructor like this
$user = new Users([
'name' => $name,
'lastName' => $lastname,
'age' => $age,
]);
$user->create();
This way you can dynamically populate your model by building the array instead of numerous method calls.
It's also worth noting that if you want to use "setters" and "getter" methods you should define the properties as protected. The reason for this is because Phalcon will automatically call the set/get methods if they exist when you assign a value to the protected property.
For example:
class User extends \Phalcon\Mvc\Model
{
protected $name;
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$user= new MyModel();
$user->name = 'Cameron'; // This will invoke User::setName
echo $user->name; // This will invoke User::getName
It is also worth noting that the properties will behave as you'd expect a protected property to behave the same as a traditional protected property if the respective method is missing. For example, you cannot assign a value to a protected model property without a setter method.

Setting a required value in Doctrine model

Is it possible to set a constraint in a Doctrine model, so that all queries using that model include this requirement? For example, if I have a Car model and I want to ensure that all results retrieved using the model have active = 1 set in the database. I could define this in each individual query, but it seems like there's probably a better way.
Cheers!
I would take advantage of their amazing pre and post hooks inside the model.
Example:
class Model_Car extends Model_Base_Car
{
public function preDqlSelect(Doctrine_Event $event)
{
$event->getQuery()->addWhere("active = ?", 1);
}
}
Although I did not test this, it should work. I have used the pre and post hooks a lot to make my life easier in the past. For instance, I had a model that wanted to save the REMOTE_ADDR on each insert and update, so I did the following to make my life easier:
class Model_Example extends Model_Base_Example
{
public function preInsert(Doctrine_Event $event)
{
$this->created_ip = $this->_getRemoteIp();
}
public function preUpdate(Doctrine_Event $event)
{
$this->updated_ip = $this->_getRemoteIp();
}
protected function _getRemoteIp()
{
return ip2long($_SERVER['REMOTE_ADDR']);
}
}
hope this helps!
I would do this in query ->andWhere('active = ?', 1), but if you are tring to do this "tricky way", you can always build your own hydrator.
here is an answer to your question: subclassing the doctrine query object.
http://brentertainment.com/2010/03/03/doctrine_query_extra-extending-the-doctrine-query-object/

Is this the correct way to do this PHP class?

Hello I am just learning more about using classes in PHP. I know the code below is crap but I need help.
Can someone just let me know if I am going in the right direction.
My goal is to have this class included into a user profile page, when a new profile object is created, I would like for it to retrieve all the profile data from mysql, then I would like to be able to display any item on the page by just using something like this
$profile = New Profile;
echo $profile->user_name;
Here is my code so far, please tell me what is wrong so far or if I am going in the right direction?
Also instead of using echo $profile->user_name; for the 50+ profile mysql fileds, sometimes I need to do stuff with the data, for example the join date and birthdate have other code that must run to convert them, also if a record is empty then I would like to show an alternative value, so with that knowlege, should I be using methods? Like 50+ different methods?
<?PHP
//Profile.class.php file
class Profile
{
//set some profile variables
public $userid;
public $pic_url;
public $location_lat;
public $location_long;
public $user_name;
public $f_name;
public $l_name;
public $country;
public $usa_state;
public $other_state;
public $zip_code;
public $city;
public $gender;
public $birth_date;
public $date_create;
public $date_last_visit;
public $user_role;
public $photo_url;
public $user_status;
public $friend_count;
public $comment_count;
public $forum_post_count;
public $referral_count;
public $referral_count_total;
public $setting_public_profile;
public $setting_online;
public $profile_purpose;
public $profile_height;
public $profile_body_type;
public $profile_ethnicity;
public $profile_occupation;
public $profile_marital_status;
public $profile_sex_orientation;
public $profile_home_town;
public $profile_religion;
public $profile_smoker;
public $profile_drinker;
public $profile_kids;
public $profile_education;
public $profile_income;
public $profile_headline;
public $profile_about_me;
public $profile_like_to_meet;
public $profile_interest;
public $profile_music;
public $profile_television;
public $profile_books;
public $profile_heroes;
public $profile_here_for;
public $profile_counter;
function __construct($session)
{
// coming soon
}
//get profile data
function getProfile_info(){
$sql = "SELECT user_name,f_name,l_name,country,usa_state,other_state,zip_code,city,gender,birth_date,date_created,date_last_visit,
user_role,photo_url,user_status,friend_count,comment_count,forum_post_count,referral_count,referral_count_total,
setting_public_profile,setting_online,profile_purpose,profile_height,profile_body_type,profile_ethnicity,
profile_occupation,profile_marital_status,profile_sex_orientation,profile_home_town,profile_religion,
profile_smoker,profile_drinker,profile_kids,profile_education,profile_income,profile_headline,profile_about_me,
profile_like_to_meet,profile_interest,profile_music,profile_television,profile_books,profile_heroes,profile_here_for,profile_counter
FROM users WHERE user_id=$profileid AND user_role > 0";
$result_profile = Database::executequery($sql);
if ($profile = mysql_fetch_assoc($result_profile)) {
//result is found so set some variables
$this->user_name = $profile['user_name'];
$this->f_name = $profile['f_name'];
$this->l_name = $profile['l_name'];
$this->country = $profile['country'];
$this->usa_state = $profile['usa_state'];
$this->other_state = $profile['other_state'];
$this->zip_code = $profile['zip_code'];
$this->city = $profile['city'];
$this->gender = $profile['gender'];
$this->birth_date = $profile['birth_date'];
$this->date_created = $profile['date_created'];
$this->date_last_visit = $profile['date_last_visit'];
$this->user_role = $profile['user_role'];
$this->photo_url = $profile['photo_url'];
$this->user_status = $profile['user_status'];
$this->friend_count = $profile['friend_count'];
$this->comment_count = $profile['comment_count'];
$this->forum_post_count = $profile['forum_post_count'];
$this->referral_count = $profile['referral_count'];
$this->referral_count_total = $profile['referral_count_total'];
$this->setting_public_profile = $profile['setting_public_profile'];
$this->setting_online = $profile['setting_online'];
$this->profile_purpose = $profile['profile_purpose'];
$this->profile_height = $profile['profile_height'];
$this->profile_body_type = $profile['profile_body_type'];
$this->profile_ethnicity = $profile['profile_ethnicity'];
$this->profile_occupation = $profile['profile_occupation'];
$this->profile_marital_status = $profile['profile_marital_status'];
$this->profile_sex_orientation = $profile['profile_sex_orientation'];
$this->profile_home_town = $profile['profile_home_town'];
$this->profile_religion = $profile['profile_religion'];
$this->profile_smoker = $profile['profile_smoker'];
$this->profile_drinker = $profile['profile_drinker'];
$this->profile_kids = $profile['profile_kids'];
$this->profile_education = $profile['profile_education'];
$this->profile_income = $profile['profile_income'];
$this->profile_headline = $profile['profile_headline'];
$this->profile_about_me = $profile['profile_about_me'];
$this->profile_like_to_meet = $profile['profile_like_to_meet'];
$this->profile_interest = $profile['profile_interest'];
$this->profile_music = $profile['profile_music'];
$this->profile_television = $profile['profile_television'];
$this->profile_books = $profile['profile_books'];
$this->profile_heroes = $profile['profile_heroes'];
$this->profile_here_for = $profile['profile_here_for'];
$this->profile_counter = $profile['profile_counter'];
}
//this part is not done either...........
return $this->pic_url;
}
}
You might want to take a look at PHP's magic methods which allow you to create a small number of methods (typically "get" and "set" methods), which you can then use to return/set a large number of private/protected variables very easily. You could then have eg the following code (abstract but hopefully you'll get the idea):
class Profile
{
private $_profile;
// $_profile is set somewhere else, as per your original code
public function __get($name)
{
if (array_key_exists($name, $this->_profile)) {
return $this->_profile[$name];
}
}
public function __set($name, $value)
{
// you would normally do some sanity checking here too
// to make sure you're not just setting random variables
$this->_profile[$name] = $value;
}
}
As others have suggested as well, maybe looking into something like an ORM or similar (Doctrine, ActiveRecord etc) might be a worthwhile exercise, where all the above is done for you :-)
Edit: I should probably have mentioned how you'd access the properties after you implement the above (for completeness!)
$profile = new Profile;
// setting
$profile->user_name = "JoeBloggs";
// retrieving
echo $profile->user_name;
and these will use the magic methods defined above.
You should look into making some kind of class to abstract this all, so that your "Profile" could extend it, and all that functionality you've written would already be in place.
You might be interested in a readymade solution - these are called object relational mappers.
You should check out PHP ActiveRecord, which should easily allow you to do something like this without writing ORM code yourself.
Other similar libraries include Doctrine and Outlet.
Don't use a whole bunch of public variables. At worst, make it one variable, such as $profile. Then all the fields are $profile['body_type'] or whatever.
This looks like a Data Class to me, which Martin Fowler calls a code smell in his book Refactoring.
Data classes are like children. They are okay as a starting point, but to participate as a grownup object, they need to take some responsibility.
He points out that, as is the case here,
In the early stages these classes may have public fields. If so, you should immediately Encapsulate Field before anyone notices.
If you turn your many fields into one or several several associative arrays, then Fowler's advice is
check to see whether they are properly encapsulated and apply Encapsulate Collection if they aren't. Use Remove Setting Method on any field that should not be changed.
Later on, when you have your Profile class has been endowed with behaviors, and other classes (its clients) use those behaviors, it may make sense to move some of those behaviors (and any associated data) to the client classes using Move Method.
If you can't move a whole method, use Extract Method to create a method that can be moved. After a while, you can start using Hide Method on the getters and setters.
Normally, a class would be created to abstract the things you can do to an object (messages you can send). The way you created it is more like a dictionary: a one-to-one mapping of the PHP syntaxis to the database fields. There's not much added value in that: you insert one extra layer of indirection without having a clear benefit.
Rather, the class would have to contain what's called a 'state', containing e.g. an id field of a certain table, and some methods (some...) to e.g. "addressString()", "marriedTo()", ....
If you worry about performance, you can cache the fields of the table, which is a totally different concern and should be implemented by another class that can be aggregated (a Cache class or whatsoever).
The main OO violation I see in this design is the violation of the "Tell, don't ask" principle.

MVC model where to put data specific checks

I'm writing my first application with Zendframework.
My question is about the Model–View–Controller (MVC) architectural pattern.
I currently have a model with refer to a database table.
Here's the classes that I currently have :
Model_Person
Model_PersonMapper
Model_DbTable_Person
Now, I see a lot of examples on the net, but all of them are simple cases of insert/update/delete.
In my situation, I have to check if a person exists, and if it doesn't, I have to insert it and retrieve the ID (I know save return the Id, but it's not exactly what I have to do, this is and example).
It's quit simple, but I want to know where to put the database logic for all the others specific cases. Some others cases might involve checks across other tables or ... whatever !
Should I add all the specific functions in my Model_XXXXMapper with something that would be very specific with the current validation/process that I want to do? like a function getIdOfThePersonByNameOrInsertIfNotExists() (sample name of course!!!)
Or should it reside in the controller with some less specifics access to my model would be validated?
In other word, where do I put all the data specifics functions or check ?
I think the real work should occur in your model objects, not in the controller. Any selects/creates that start with the person table would be in the DbTable_Person object, things like:
// DbTable_Person
// returns sets of or single Person objects
public function createByName( $name ) // perhaps throws exception if name already exists
public function findById( $id )
public function findByName( $name )
public function findHavingAccount( $account_id ) // references another table
// controller
// with your example, like what Galen said,
// I would let the controller handle this logic
$person = $person_table->findByName($name);
if ( !$person ) {
$person = $person_table->createByName($name);
}
if ( !$person ) { throw new Zend_Exception('huh?'); }
$id = $person->id; // you wanted the ID
I would definitely split the function up into search/create functions.
Here's a basic implementation...
$personTG = new Model_PersonTableGateway;
if ( !$person = $personTG->findByName( $name ) ) {
$person = new Model_Person;
$person->name = $name;
// other variables
$newPersonId = $personTG->create( $person ); // creates a new person
}
I use table gateway. You can substitute your class for the TG.
You can have the create() function return just the id of the newly created person, or the entire person...it's up to you.
You might be interested in Zend_Validate_Db_NoRecordExists and its sister. If you are using Zend_Form you can add this validator to your form element. Many folks use Zend_Form to validate and filter data before they reach the domain model.
If you are not using Zend_Form, you can simply use this validation class in your service layer. A simple service class could be something like
`
class Service_Person_Validate
{
public function creatable($data)
{ // return true|false
}
}

Categories