ApiGility - Returning custom collections - php

My goal is to return a custom collection for a findAll() query and to deliver this to HAL in order to ensure that its _links are formatted correctly. I originally thought I would simply do this programmatically however this seems to be the wrong way of doing this.
The problem I face is that the data I require is not from a single table, but rather from multiple tables (joins) and I am unable to work out how to do this properly.
I have the following entities:
Stone entity: A standard table with a join to some attributes that I would like to return in my feed
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Stone\Entity\StAttribute")
* #ORM\JoinTable(name="st_stone_attribute",
* joinColumns={#ORM\JoinColumn(name="stone_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="attribute_id", referencedColumnName="id")}
* )
*
* #var Collection
* #access private
*/
private $attribute;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private $name;
etc...
The attribute entity is a standard table:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=30, nullable=false)
*/
private $name;
My resource calls:
public function fetchAll($params = array())
{
return $this->stoneMapper->fetchAll();
}
My mapper file:
public function fetchAll()
{
$qb = $this->stoneRepository->createQueryBuilder('u')
->where('u.state=:state')
->setParameter('state' , 1 );
$adapter = new DoctrineAdapter( new ORMPaginator( $qb ) );
$collection = new StoneCollection($adapter);
return $collection;
}
My collection
use Zend\Paginator\Paginator;
class StoneCollection extends Paginator
{
}
Screen shot of the results here: http://screencast.com/t/vgm34s92dsk2
As you can see from the screen shot "attribute" and other similar fields are not being populated...
So my question is this: how do I ensure that the join tables are populated in the feed?

You will need to fetch join your associations. You can read on this in the Doctrine 2 documentation here.
In your case it would look as follows:
$qb = $this->stoneRepository->createQueryBuilder('s')
->addSelect('a')
->leftJoin('s.attribute', 'a')
->where('s.state = :state')
->setParameter('state' , 1 );
It will also be necessary to have either a hydrator for your StAttribute in your MetadataMap or there should otherwise be some code implemented to extract the StAttribute properties.
You can of course also do this in the fetch method itself, but that is not so pretty.
The object will continue to render as {} in case you do not extract or convert the object to something that can be serialized to valid json format (either a Hal resource or collection instance, a (json) string or a JsonSerializable).

Related

Unrecognized id Field with Symfony 3 and FOSUserBundle

I keep getting the error in the title when I want to login using the FOSUserBundle on Symfony. The problem is, I already have an "id" for my User table from my database so I don't want to create an "id" field like they ask on the FOSUserBundle guide. I don't understand why it would give me this error when there is no more "id" field in my code.
Is this "id" field mandatory?
Here is the code of my User class (here called "Utilisateurs")`use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* Utilisateurs
*
* #ORM\Table(name="utilisateurs", indexes={#ORM\Index(name="FK_UTILISATEURS_id_sexe", columns={"id_sexe"}), #ORM\Index(name="FK_UTILISATEURS_id_niveau", columns={"id_niveau"})})
* #ORM\Entity
*/
class Utilisateurs extends BaseUser
{
public function __construct()
{
parent::__construct();
}
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=25, nullable=true)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="prenom", type="string", length=25, nullable=true)
*/
private $prenom;
/**
* #var \DateTime
*
* #ORM\Column(name="date_naissance", type="date", nullable=true)
*/
private $dateNaissance;
/**
* #var string
*
* #ORM\Column(name="url_photo", type="string", length=100, nullable=true)
*/
private $urlPhoto;
/**
* #var integer
*
* #ORM\Column(name="id_utilisateur", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idUtilisateur;
/**
* #var \Site\UserBundle\Entity\Sexes
*
* #ORM\ManyToOne(targetEntity="Site\UserBundle\Entity\Sexes")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_sexe", referencedColumnName="id_sexe")
* })
*/
private $idSexe;
/**
* #var \Site\UserBundle\Entity\Niveaux
*
* #ORM\ManyToOne(targetEntity="Site\UserBundle\Entity\Niveaux")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_niveau", referencedColumnName="id_niveau")
* })
*/
private $idNiveau;`
As you can see I already have an "id_utilisateur" field which is the id of this entity.
And here is the code of the entity information in XML: The XML Code
Also here is a screenshot of the error I get when I try to log in: The Error
I think the problem is that per convention the id field is often called just id and in some places FOS UserBundle is expecting exactly that, e.g. in the UserProvider.
There are a few ways you can get around this. For instance you could just write your own UserProvder (using the one linked above as a reference) where you substitute the id with your field. You might have to do this in other places as well.
The easier solution would be to just change your entity to something like this:
/**
* #var integer
*
* #ORM\Column(name="id_utilisateur", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
public function getId() { return $this->id; }
Similarly in xml this would look like this:
<id name="id" column="id_utilisateur" type="integer">
<generator strategy="IDENTITY" />
</id>
This way in your entity you will use the expected property and accessor method, but in the background it will map to the database field id_utilisateur, so you you don't have to make any changes to your database.
This should already solve your problems. When a new user is generated Doctrine will take map $user->getId() to user_table.id_utilisateur automatically. If your existing code is making use of the old get-method you could just keep it around and mark it as deprecated:
/**
* #deprecated Use getId() instead.
*/
public function getIdUtilisateur()
{
return $this->getId();
}

How to join entites like they are tables in Doctrine 2?

The documentation on how to retrieve joined entites as one object is very sparse, and most Stack Overflow questions on the subject are many years old.
I have a symfony project up and running with a database schema fully mapped in Doctrine. In my controllers I am able to run these two queries one after the other and they work fine.
$page = $this->getDoctrine()
->getRepository('PageBundle:SitePages')
->findByprodpageid($id);
$matrices = $this->getDoctrine()
->getRepository('PageBundle:SiteMatrices')
->findByprodpageid($id);
however both of them contain the attribute prodpageid and I would like to join the two entities on this column and receive one object containing all column values from both tables.
I am building this on top of an existing database structure so anything to do with changing the database structure etc is out of the question.
I have added annotations in my entities to specify which columns should be joined, in a ManyToOne relationship. But how do I activate that relation and receive the joined object?
Thanks for any info on the subject.
EDIT: Here are my relationships from the entities
//Entities/SitePages
/**
* #var integer
*
* #ORM\Column(name="ProdPageID", type="smallint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #ORM\OneToMany(targetEntity="SiteMatrices")
* #ORM\JoinColumn(name="prodpageid", referencedColumnName="prodpageid")
*/
private $prodpageid;
//Entities/SiteMatrices
/**
* #var integer
*
* #ORM\Column(name="ProdPageID", type="smallint", nullable=false)
* #ORM\ManyToOne(targetEntity="SitePages")
* #ORM\JoinColumn(name="prodpageid", referencedColumnName="prodpageid")
*
*/
private $prodpageid;
You are saying that a Page has many Matrices. I will make some changes by your permission in mapping annotations:
/**
* Entities/SitePages
*
* #var integer
*
* #ORM\Column(name="ProdPageID", type="smallint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #ORM\OneToMany(targetEntity="SiteMatrices")
* #ORM\JoinColumn(name="prodpageid", referencedColumnName="prodpageid")
*/
private $matrices;
/**
* #return ArrayCollection
*/
public function getMatrices(){
return $this->matrices;
}
/**
* #param Entities/SiteMatrices[]
* #return $this
*/
public function setMatrices($matrices){
$this->matrices = $matrices;
return $this;
}
and
/**
* Entities/SiteMatrices
*
* #var integer
*
* #ORM\Column(name="ProdPageID", type="smallint", nullable=false)
* #ORM\ManyToOne(targetEntity="SitePages")
* #ORM\JoinColumn(name="prodpageid", referencedColumnName="prodpageid")
*
*/
private $page;
/**
* #return Entities/SitePages
*/
public function getPage(){
return $this->page;
}
/**
* #param Entities/SitePages
* #return $this
*/
public function setPage($page){
$this->page = $page;
return $this;
}
Now if you query the Pages with this DQL:
$pages = $this->getDoctrine()
->getRepository('PageBundle:SitePages')
->findByprodpageid($id);
Then you could get each page's matrices simply by traversing on matrices association:
foreach($pages as $page){
$matrices = $page->getMatrices(); // will give you an ArrayCollection of all matrices objects joined by prodpageid to this page.
}
Hope I did not get you wrong and it helps.

Symfony find field

I have a Developers entity and table named CodeUserReference
class CodeUserReference
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*#ORM\ManyToOne(targetEntity="Artel\CustomerBundle\Entity\Developers", inversedBy="newreference")
*/
protected $alluser;
/**
* #ORM\Column(type="string", length=255)
*/
protected $codereference;
and Developers
class Developers extends SUser
{
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, unique=false, nullable=false)
* #Assert\Length(min=3, max=255)
* #Assert\NotBlank
*/
protected $email;
/**
* #ORM\ManyToMany(targetEntity="Artel\CustomerBundle\Entity\CodeUserReference", inversedBy="alluser")
*/
protected $newreference;
I have get query in my Action
public function profileGetAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$code_user_reference = $em->getRepository('ArtelCustomerBundle:CodeUserReference')->findOneByCodereference($request->query->get('reference'));
$user_by_email = $em->getRepository('ArtelCustomerBundle:Developers')->findOneByEmail($request->query->get('email'));
if (!empty($code_user_reference) && empty($user_by_email))
{
$id = $code_user_reference->getAlluser()->getId();
$user_by_reference = $em->getRepository('ArtelCustomerBundle:Developers')->findOneById($id);
$user_by_reference_json = $em->getRepository('ArtelCustomerBundle:Developers')->createQueryBuilder('d')
->where('d.id= :id')
->groupBy('d.id')
->setParameter('id', $code_user_reference->getAlluser()->getId())
->getQuery()->getArrayResult();
echo json_encode( array('user' => $user_by_reference_json));
die;
}
If(empty($code_user_reference) && !empty($user_by_email))
.......
I get Id in code_user_reference, then I find Developers objects with this ID, then I create a QueryBuilder and in this QueryBuilder I find the developers objects again, for JSON table. I find this very hard, who knows easier practics ?
There is certanly mismapping.
if you have #ORM\ManyToOne from one site, there should be #ORM\OneToMany from other.
Or, if you need many-to-many relationship, there should be #ORM\ManyToMany on both entities.
Also, relation should be mapped at one side. If you have (targetEntity="Artel\CustomerBundle\Entity\Developers", inversedBy="newreference") it means it should be mapped here
something like
#ORM\ManyToOne(targetEntity="Artel\CustomerBundle\Entity\Developers", inversedBy="newreference")
#ORM\JoinColumn(name="id_developer", referencedColumnName="id")
and at the other side must be mappedBy instead of inversedBy
* #ORM\OneToMany(targetEntity="Artel\CustomerBundle\Entity\CodeUserReference", mappedBy="alluser")

How to add extra WHERE clauses when retrieving relationships in Doctrine2

I have two entities Post and Comment.
Structure:
Post:
id
title
body
Comment:
id
post_id
body
active
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\OneToMany(
* targetEntity="Comment",
* mappedBy="post"
* )
*/
private $comments;
class Comment
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\ManyToOne(
* targetEntity="Post",
* inversedBy="comments"
* )
* #ORM\JoinColumn(
* name="post_id",
* referencedColumnName="id"
* )
*/
private $post;
As a result when I want to get all comments for a post I use $post->getComments() and it works.
How I can add extra Where clauses into this relationship if I want to get only posts with active = 1.
I know that I can do it by DQL or queryBuilder but I want to know how I can do it by mapping
I think the cleanest way to retrieve only active comments is to use Doctrine's Criteria object in the getComments method of your Post entity
use Doctrine\Common\Collections\Criteria;
and
public function getComments()
{
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
return $this->comments->matching($criteria);
}
Edit
If you want to prevent multiple queries each time you retrieve the active comments, you'll need to store them in a local variable. Instead of modifying getComments, you could add $active_comments and getActiveComments, which will populate $active_comments and only query the db if $active_comments is false.
class Post {
private $active_comments;
public function getActiveComments()
{
if(!$this->active_comments) {
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
$this->active_comments = $this->comments->matching($criteria);
}
return $this->active_comments;
}

Doctrine findOneBy method not working

I am creating small application with just two entities, Order and Shipment.
The Shipment entity is as follows: (methods removed to keep it short)
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $username
*
* #ORM\Column(name="username", type="string", length=255)
*/
private $username;
/**
* #var string $password
*
* #ORM\Column(name="password", type="string", length=255)
*/
private $password;
/**
* #var integer $order_id
*
* #ORM\Column(name="order_id", type="integer")
*/
private $order_id;
/**
* #var smallint $payment_type
*
* #ORM\Column(name="payment_type", type="smallint")
*/
private $payment_type;
In my controller I am trying to query using the order_id but my findOneByOrderId method is not working.
$orderExists = $this->getDoctrine()
->getRepository('ShipBundle:Shipment')
->findOneByOrderId($orderId);
var_dump($orderExists); die();
The error I get is:
Entity 'ShipBundle\Entity\Shipment' has no field 'orderId'. You can therefore not call 'findOneByOrderId' on the entities' repository.
If I am not wrong, Doctrine find methods join the variables at underscores and capitalize them. What am I doing wrong?
I managed to solve the problem with the hint from pomaxa and Doctrine2 documentation.
The correct code would be:
$orderExists = $this->getDoctrine()
->getRepository('ShipBundle:Shipment')
->findOneBy(array('order_id' => $orderId));
explained at: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-objects.html#by-simple-conditions
Thanks everyone for the help. I appreciate it.
You could use the inbuilt relationship capabilities of Doctrine2 instead of using an id of order in your entity Shipment manually
That way you would have a relationship Doctrine is aware of.
$orders = $shipment->getOrders();
Look here: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html
Problem in this line
private $order_id;
Use it
private $orderId;
It is ok. For db you will have order_id.
Just to clarify, the reason for the error was that you needed to pass an Array into the findOneBy();
This is wrong: , ->findOneByOrderId($orderId); in
$orderExists = $this->getDoctrine()
->getRepository('ShipBundle:Shipment')
->findOneByOrderId($orderId);
An array must be passed. array('order_id' => $orderId)
$orderExists = $this->getDoctrine()
->getRepository('ShipBundle:Shipment')
->findOneBy(array('order_id' => $orderId));
OR SHORTHAND ['order_id'=> $orderId] as long as you are in PHP >= 5.4
$orderExists = $this->getDoctrine()
->getRepository('ShipBundle:Shipment')
->findOneBy(['order_id'=> $orderId]);

Categories