append a related Model in Phalcon - php

I wrote a vcard class with Phalcon in PHP. The vCard Model is initialized like this.
// Inside the BS_VCard class
public function initialize(){
$this->hasMany("id","BS_VCardElement","vCardId",array(
"alias" => "elements",
'foreignKey' => array(
'action' => Phalcon\Mvc\Model\Relation::ACTION_CASCADE
)
));
}
Its elements are initialized like this
// Inside the BS_VCardElement class
public function initialize(){
$this->belongsTo("vCardId","BS_VCard","id",array("alias" => "vCard"));
...
}
If a user reads a vCard and adds another element, it doesn't work as expected. To simplify the use I added some fascade methods like this
public function addDateOfBirth($date){
$element = new BS_VCardElement();
$element->setName("BDAY");
$element->addValue($date);
// This doesn't work
$this->elements[] = $element;
}
The Docs/Storing related records do not explain how to append fresh data like this to the related table.
I also tried this
$this->elements[] = array_merge($this->elements,array($element));
But the save method seems to ignore the added element. Save() returns true.

This question has been asked a couple of months ago but since I ran into a similar issue I decided to share my results anyway.
And here's what I found. Lower case aliases ('elements') don't seem to work whereas upper case aliases ('Elements') do.
To add one element you can do this;
$this->Elements = $element;
To add multiple elements you can do this;
$elements = array($element1, $element2);
$this->Elements = $elements;
After that you have to save the vcard before accessing the elements again. If you don't, phalcon will just return a result set with only the elements already in the database. (Not sure if this can be changed somehow.)
And here's the documentation (where all this is not mentioned): http://docs.phalconphp.com/en/latest/reference/models.html#storing-related-records

According to the Phalcon source code, the Resultset object is immutible.
/**
* Resultsets cannot be changed. It has only been implemented to
* meet the definition of the ArrayAccess interface
*
* #param int index
* #param \Phalcon\Mvc\ModelInterface value
*/
public function offsetSet(var index, var value)
{
throw new Exception("Cursor is an immutable ArrayAccess object");
}
It appears that replacing the element with an array is the only way to implement an "append" or modification of the resultset (other than delete which IS supported).
Of course this breaks the \Phalcon\Mvc\Model::_preSaveRelatedRecords() because the function ignores the class properties and refetches the related from the Model Manager (and resets the model::$element attribute at the end).
I feel frustrated by this because appending objects to a collection seems like a very common task and not having a clear method in which to add new items to a parent seems like a design flaw.

I think related elements might have some magic functionality invoked when you set the properties, so simply using $this->elements[] (evidently) doesn't work. Perhaps try re-setting the entire variable:
public function addDateOfBirth($date){
$element = new BS_VCardElement();
$element->setName("BDAY");
$element->addValue($date);
$elements = $this->elements;
$elements[] = $element;
$this->elements = $elements;
}

Related

How to set doctrine associations?

I know that association property in entity is implements \Doctrine\Common\Collections\Collection. I know that in constructor such properties should be initialized:
$this->collection = new \Doctrine\Common\Collections\ArrayCollection()
I know that I can modify collections using ArrayCollection#add() and ArrayCollection#remove(). However I have a different case.
Suppose I have a new simple array of associative entities. Using existing methods I need to check every element in array: if entity collection has it. If no - add array element to entity collection. In addition to this, I need to check every element in entity collection. If any collection element is absent in new array, then I need to remove it from collection. So much work to do trivial thing.
What I want? To have the setProducts method implemented:
class Entity {
private $products;
// ... constructor
public function setProducts(array $products)
{
// synchronize $products with $this->products
}
}
I tried: $this->products = new ArrayCollection($products). However this makes doctrine remove all products and add those ones from $products parameter. I want similar result but without database queries.
Is there any built in solution in Doctrine for such case?
Edit:
I would like to have a method in ArrayCollection like fromArray which would merge elements in collections removing unneeded. This would just duplicate using add/remove calls for each element in collection argumen manually.
Doctrine collections do not have a "merge"-feature that will add/remove entities from an array or Collection in another Collection.
If you want to "simplify" the manual merge process you describe using add/remove, you could use array_merge assuming both arrays are not numeric, but instead have some kind of unique key, e.g. the entity's spl_object_hash:
public function setProducts(array $products)
{
$this->products = new ArrayCollection(
array_merge(
array_combine(
array_map('spl_object_hash', $this->products->toArray()),
$this->products->toArray()
),
array_combine(
array_map('spl_object_hash', $products),
$products->toArray()
)
)
);
}
You might want to use the product id instead of spl_object_hash as 2 products with the same id, but created as separate entities - e.g. one through findBy() in Doctrine and one manually created with new Product() - will be recognized as 2 distinct products and might cause another insert-attempt.
Since you replace the original PersistentCollection holding your previously fetched products with a new ArrayCollection this might still result in unneeded queries or yield unexpected results when flushing the EntityManager, though. Not to mention, that this approach might be harder to read than explicitly calling addElement/removeElement on the original Collection instead.
I would approach it by creating my own collection class that extends Doctrine array collection class:
use Doctrine\Common\Collections\ArrayCollection;
class ProductCollection extends ArrayCollection
{
}
In the entity itself you would initialise it in the __constructor:
public function __construct()
{
$this->products = new ProductCollection();
}
Here, Doctrine will you use your collection class for product results. After this you could add your own function to deal with your special merge, perhaps something:
public function mergeProducts(ProductCollection $products): ProductCollection
{
$result = new ProductCollection();
foreach($products as $product) {
$add = true;
foreach($this->getIterator() as $p) {
if($product->getId() === $p->getId()) {
$result->add($product);
$add = false;
}
}
if($add) {
$result->add($product);
}
}
return $result;
}
It will return a brand new product collection, that you can replace your other collection in the entity. However, if the entity is attached and under doctrine control, this will render SQL at the other end, if you want to play with the entity without risking database updates you need to detach the entity:
$entityManager->detach($productEntity);
Hopes this helps

How to copy a CakePHP Entity

I'm writing a method that copies an object. Instead of manually setting each property manually, it would be more robust to just loop over the original object's properties...
//Booo
$new->name = $old->name;
$new->color = $old->color;
...
//Oh yeah...
foreach ($old as $prop=>$val){
$new->$prop = $val;
}
unset $new->id;
It appears that CakePHP entities cannot be iterated over in this way. I tried using $old->toArray(), which basically works... but has the drawback of converting all the associations to arrays also, which is screwing this up for me down stream.
How do I loop over the $old properties without converting all the data types?
Update:
Mark brought to my attention the existence of a __clone() method. Sounds like it does exactly what I need but I'm still figuring out how to use it.
You can use $entity->visualProperties()
foreach($old->visualProperties() as $property) {
if($new->has($property))
$new->set($property, $old->get($property));
After looking at this for a while, and discovering there is no __clone() function for entities, at least in 3.8, I have worked out how to do it, with the hint from DouglasSantos :
//Find out the entity classname
$classname = get_class($entity);
//Instanciate a new object of that class
$clone = new $classname;
//Use visibleProperties to clone it
foreach($entity->visibleProperties() as $property)
if($clone->has($property))
$clone->set($property, $entity->get($property));
Of course you could combine the first 2 lines into one line, but I have split it out for clarity.
UPDATE: I have discovered if you use the has->($property) check it will skip many of the fields. So the corrected answer is :
//Find out the entity classname
$classname = get_class($entity);
//Instanciate a new object of that class
$clone = new $classname;
//Use visibleProperties to clone it
foreach($entity->visibleProperties() as $property)
$clone->$property = $entity->$property;
It is actually much easier to use the Table Object:
// Assuming your model is called "Documents"
// If you are in the Controller, you can just use `$this->Documents`
instead of fetching the Table from the Registry
use Cake\ORM\TableRegistry;
$table = TableRegistry::getTableLocator()->get('Documents');
// newEntity() creates a new Entity from an array of data
$documentCopy = $table->newEntity(
// extract() extracts the given properties as an associative array
$document->extract(
// getVisible() will get all visible properties as an array
$document->getVisible()
)
);

How to manually create a new empty Eloquent Collection in Laravel 4

How do we create a new Eloquent Collection in Laravel 4, without using Query Builder?
There is a newCollection() method which can be overridden by that doesn't really do job because that is only being used when we are querying a set result.
I was thinking of building an empty Collection, then fill it with Eloquent objects. The reason I'm not using array is because I like Eloquent Collections methods such as contains.
If there are other alternatives, I would love to hear them out.
It's not really Eloquent, to add an Eloquent model to your collection you have some options:
In Laravel 5 you can benefit from a helper
$c = collect(new Post);
or
$c = collect();
$c->add(new Post);
OLD Laravel 4 ANSWER
$c = new \Illuminate\Database\Eloquent\Collection;
And then you can
$c->add(new Post);
Or you could use make:
$c = Collection::make(new Post);
As of Laravel 5. I use the global function collect()
$collection = collect([]); // initialize an empty array [] inside to start empty collection
this syntax is very clean and you can also add offsets if you don't want the numeric index, like so:
$collection->offsetSet('foo', $foo_data); // similar to add function but with
$collection->offsetSet('bar', $bar_data); // an assigned index
I've actually found that using newCollection() is more future proof....
Example:
$collection = (new Post)->newCollection();
That way, if you decide to create your own collection class for your model (like I have done several times) at a later stage, it's much easier to refactor your code, as you just override the newCollection() function in your model
Laravel >= 5.5
This may not be related to the original question, but since it's one of the first link in google search, i find this helpful for those like me, who are looking for how to create empty collection.
If you want to manually create a new empty collection, you can use the collect helper method like this:
$new_empty_collection = collect();
You can find this helper in Illuminate\Support\helpers.php
snippet:
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* #param mixed $value
* #return \Illuminate\Support\Collection
*/
function collect($value = null)
{
return new Collection($value);
}
}
Just to add on to the accepted answer, you can also create an alias in config/app.php
'aliases' => array(
...
'Collection' => Illuminate\Database\Eloquent\Collection::class,
Then you simply need to do
$c = new Collection;
In Laravel 5 and Laravel 6 you can resolve the Illuminate\Database\Eloquent\Collection class out of the service container and then add models into it.
$eloquentCollection = resolve(Illuminate\Database\Eloquent\Collection::class);
// or app(Illuminate\Database\Eloquent\Collection::class). Whatever you prefer, app() and resolve() do the same thing.
$eloquentCollection->push(User::first());
For more information about understanding resolving objects out of the service container in laravel take a look here:
https://laravel.com/docs/5.7/container#resolving
I am using this way :
$coll = new Collection();
$coll->name = 'name';
$coll->value = 'value';
$coll->description = 'description';
and using it as normal Collection
dd($coll->name);
It is better to use the Injection Pattern and after $this->collection->make([]) than new Collection
use Illuminate\Support\Collection;
...
// Inside of a clase.
...
public function __construct(Collection $collection){
$this->collection = $collection;
}
public function getResults(){
...
$results = $this->collection->make([]);
...
}
What worked for me was to name the use namespace and instantiate it directly:
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
# Usage
$this->latest_posts = new EloquentCollection();
Allowed me to merge two data subsets of eloquent collection results, this maintains the relationships - a regular collection (collect()) loses relationship and probably some more metadata.
$limit = 5;
$this->latest_posts = new EloquentCollection();
$pinned_posts = PinnedPostReference::where('category', $category)->get();
if($pinned_posts->count() > 0) {
foreach($pinned_posts as $ppost) {
$this->latest_posts->push($ppost->post);
}
}
# Another Eloquent result set ($regular_posts)
foreach($regular_posts as $regular_post) {
$this->latest_posts->push($regular_post);
}

Activerecord-association: create new object (find class)

I have an model with a relation, and I want to instantiate a new object of the relations type.
Example: A person has a company, and I have a person-object: now I
want to create a company-object.
The class of the companyobject is defined in the relation, so I don't think I should need to 'know' that class, but I should be able to ask the person-object to provide me with a new instance of type company? But I don't know how.
This is -I think- the same question as New model object through an association , but I'm using PHPActiveRecord, and not the ruby one.
Reason behind this: I have an abstract superclass person, and two children have their own relation with a type of company object. I need to be able to instantiate the correct class in the abstract person.
A workaround is to get it directly from the static $has_one array:
$class = $this::$has_one[1]['class_name'];
$company = new $class;
the hardcoded number can of course be eliminated by searching for the association-name in the array, but that's still quite ugly.
If there is anyone who knows how this is implemented in Ruby, and how the phpactiverecord implementation differs, I might get some Ideas from there?
Some testing has revealed that although the "search my classname in an array" looks kinda weird, it does not have any impact on performance, and in use it is functional enough.
You can also use build_association() in the relationship classes.
Simplest way to use it is through the Model's __call, i.e. if your relation is something like $person->company, then you could instantiate the company with $company = $person->build_company()
Note that this will NOT also make the "connection" between your objects ($person->company will not be set).
Alternatively, instead of build_company(), you can use create_company(), which will save a new record and link it to $person
In PHPActiveRecord, you have access to the relations array. The relation should have a name an you NEED TO KNOW THE NAME OF THE RELATIONSHIP/ASSOCIATION YOU WANT. It doesn't need to be the classname, but the classname of the Model you're relating to should be explicitly indicated in the relation. Just a basic example without error checking or gritty relationship db details like linking table or foreign key column name:
class Person extends ActiveRecord\Model {
static $belongs_to = array(
array('company',
'class_name' => 'SomeCompanyClass')
);
//general function get a classname from a relationship
public static function getClassNameFromRelationship($relationshipName)
foreach(self::$belongs_to as $relationship){
//the first element in all relationships is it's name
if($relationship[0] == $relationshipName){
$className = null;
if(isset($relationship['class_name'])){
$className = $relationship['class_name'];
}else{
// if no classname specified explicitly,
// assume the clasename is the relationship name
// with first letter capitalized
$className = ucfirst($relationship);
}
return $className
}
}
return null;
}
}
To with this function, if you have a person object and want an object defined by the 'company' relationship use:
$className = $person::getClassNameFromRelationship('company');
$company = new $className();
I'm currently using below solution. It's an actual solution, instead
of the $has_one[1] hack I mentioned in the question. If there is a
method in phpactiverecord I'm going to feel very silly exposing
msyelf. But please, prove me silly so I don't need to use this
solution :D
I am silly. Below functionality is implemented by the create_associationname call, as answered by #Bogdan_D
Two functions are added. You should probably add them in the \ActiveRecord\Model class. In my case there is a class between our classes and that model that contains extra functionality like this, so I put it there.
These are the 2 functions:
public function findClassByAssociation($associationName)
Called with the name of the association you are looking for.
Checks three static vars (has_many,belongs_to and has_one) for the association
calls findClassFromArray if an association is found.
from the person/company example: $person->findClassByAssociation('company');
private function findClassFromArray($associationName,$associationArray)
Just a worker-function that tries to match the name.
Source:
/**
* Find the classname of an explicitly defined
* association (has_one, has_many, belongs_to).
* Unsure if this works for standard associations
* without specific mention of the class_name, but I suppose it doesn't!
* #todo Check if works without an explicitly set 'class_name', if not: is this even possible (namespacing?)
* #todo Support for 'through' associations.
* #param String $associationName the association you want to find the class for
* #return mixed String|false if an association is found, return the class name (with namespace!), else return false
* #see findClassFromArray
*/
public function findClassByAssociation($associationName){
//$class = $this::$has_one[1]['class_name'];
$that = get_called_class();
if(isset($that::$has_many)){
$cl = $this->findClassFromArray($associationName,$that::$has_many);
if($cl){return $cl;}
}
if(isset($that::$belongs_to)){
$cl = $this->findClassFromArray($associationName,$that::$belongs_to);
if($cl){return $cl;}
}
if(isset($that::$has_one)){
$cl = $this->findClassFromArray($associationName,$that::$has_one);
if($cl){return $cl;}
}
return false;
}
/**
* Find a class in a php-activerecord "association-array". It probably should have a specifically defined class name!
* #todo check if works without explicitly set 'class_name', and if not find it like standard
* #param String $associationName
* #param Array[] $associationArray phpactiverecord array with associations (like has_many)
* #return mixed String|false if an association is found, return the class name, else return false
* #see findClassFromArray
*/
private function findClassFromArray($associationName,$associationArray){
if(is_array($associationArray)){
foreach($associationArray as $association){
if($association['0'] === $associationName){
return $association['class_name'];
}
}
}
return false;
}

PHP custom object casting

I have a custom class object in PHP named product:
final class product
{
public $id;
public $Name;
public $ProductType;
public $Category;
public $Description;
public $ProductCode;
}
When passing an object of this class to my Data Access Layer I need to cast the object passed into a type of the product class so I can speak to the properties within that function. Since type casting in PHP works only with basic types what is the best solution to cast that passed object?
final class productDAL
{
public function GetItem($id)
{
$mySqlConnection = mysql_connect('localhost', 'username', 'password');
if (!$mySqlConnection) { trigger_error('Cannot connect to MySql Server!'); return; }
mysql_select_db('databaseName');
$rs = mysql_query("SELECT * FROM tblproduct WHERE ID='$id';");
$returnObject = mysql_fetch_object($rs, 'product');
return $returnObject;
}
public function SaveItem($objectToSave, $newProduct = false)
{
$productObject = new product();
$productObject = $objectToSave;
echo($objectToSave->Name);
$objectToSave->ID;
}
}
Right now I am creating a new object cast as a type of product and then setting it equal to the object passed to the function. Is there a better way of accomplishing this task? Am I going about the wrong way?
EDITED FOR CLARITY - ADD FULL PRODCUTDAL CLASS
You don't need to cast the object, you can just use it as if it was a product.
$name = $objectToSave->Name;
I´m not sure what you are trying to achieve, but if $objectToSave is already of class product:
You can simply call $objectToSave->SaveItem() (assuming SaveItem() is part of the product class) and access it´s properties in the function like $this->Name, etc.;
In your code $productObject and $objectToSave will hold a reference to the same object.
Type casts in PHP are done like this:
$converted = (type) $from;
Note, that this won't work if the object types are not compatible (if for example $form happens to be a string or object of mismatching type).
But usual solution (called Active Record pattern, present for example in Zend Framework) is to have a base class for a database item called Row. Individual items (for example the class product from your sample) then inherit from this class.
Typical ZF scenario:
$table = new Product_Table();
$product = $table->find($productId); // load the product with $productId from DB
$product->someProperty = $newPropertyValue;
$product->Save(); // UPDATE the database
Which is IMO much better than your solution.
EDIT:
You can't cast between two unrelated objects, it is not possible.
If you want to use the DAL like this, skip the "product" object and go for simple associative array. You can enumerate over its members with foreach, unlike object's properties (you could use reflection, but that's overkill).
My recommendation: Go for the Active Record pattern (it is easy to implement with magic methods). It will save you a lot of trouble.
Currently, you are creating a new Product, then discarding it immediately (as its reference is replaced by $objectToSave.) You will need to copy its properties one by one, I regret.
foreach (get_object_vars($objectToSave) as $key => $value)
{
$product->$key = $value;
}
(If the properties of $objectToSave are private, you will need to a expose a method to_array() that calls get_object_vars($this).)

Categories