Based on a private messaging app, my user entity currently have two methods to retrieve messages :
One to get the messages sent by the user
/**
* #ORM\OneToMany(targetEntity="App\Entity\MessagePrive", mappedBy="emetteur")
*/
private $messagesPrivesEmis;
/**
* #return Collection|MessagePrive[]
*/
public function getMessagesPrivesEmis(): Collection {
return $this->messagesPrivesEmis;
}
and another one to get the messages received from other users
/**
* #ORM\OneToMany(targetEntity="App\Entity\MessagePrive", mappedBy="recepteur")
*/
private $messagesPrivesRecus;
/**
* #return Collection|MessagePrive[]
*/
public function getMessagesPrivesRecus(): Collection {
return $this->messagesPrivesRecus;
}
The first method get the messages where emetteur is equal to user id, while the second get the messages where recepteur is equal to user id. Both are Symfony default methods
Is it possible to "merge" those two methods so it get all messages sent and received by the user in one single query?
Or should I resort to custom DQL?
public function getMerged(): Collection {
return new ArrayCollection(
array_merge(this->messagesPrivesEmis->toArray(), $this->messagesPrivesRecus->toArray())
);
}
Related
I have a Thread that I am indexing to Algolia and i want to be able to only allow user to search by the title and body of the thread.
So in my thread model:
/**
* Get the index name for the model.
*
* #return string
*/
public function searchableAs()
{
return 'title';
}
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
unset($array['created_at'],$array['updated_at']);
return $array;
}
So the first method is to index a specific column of the thread table and the second method is to choose which columns to place it in algolia.
I guess my question is what is searchableAs() for as I am confused with the explanation by the doc?
I've started creating a RESTful API (well, I did my best, I'm trying to follow the patterns) and I have stumbled upon a scenario that I'm not really sure how to handle. I will explain the current structure:
My application has 4 controllers:
Customers
Payments
Log
Taking as example the Customers controller, I have defined the following actions:
GET /customers: returns a list of customers
POST /customers: creates a new customer
GET /customers/{id}: returns the customer with the provided id
PUT /customers/{id}: updates the customer with the provided id
DELETE /customers/{id}: destroys the customer
This is the full code of the Customer controller:
namespace App\Http\Controllers;
use App\Customer;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
return Customer::all();
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$customer = Customer::create($request->all());
return response()->json($customer, 201);
}
/**
* Display the specified resource.
*
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function show(Customer $customer)
{
return $customer;
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function update(Request $request, Customer $customer)
{
$customer->update($request->all());
return response()->json($customer, 200);
}
/**
* Remove the specified resource from storage.
*
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function destroy(Customer $customer)
{
$customer->delete();
return response()->json(null, 204);
}
}
The code is very similar in the other controllers. It's also important to note that:
A Customer can have multiple Payments
A Customer can have multiple records in the Log
The problem starts here:
I need to display in the front-end a summary page with all customer data (name, email, registration date, etc) and a box showing the number of payments made and another box showing the number of entries in the Log.
Do I need to make 3 requests? (One to /customers/id, other to customers/id/payments and other to customers/id/logs)
If I return all the customer related data in the customers/id call, am I breaking the RESTful convention?
I am using apigility, but my answer still will be related to your question. According to the REST terminology (which could be find here https://apigility.org/documentation/intro/first-rest-service#terminology ) You are talking about entity and collection.
/customers/id - entity,
/customers/id/payments - collection,
/customers/id/logs - collection.
These are 3 different requests. So, yes, you need make 3 different requests.
But, to be honest, if you don't need pagination over payments and logs you can have only one request to /customers/id and within response you can have fields with array
{
"_links": {
"self": {
"href": "http://localhost:8080/status/3c10c391-f56c-4d04-a889-bd1bd8f746f0"
}
},
"id": "3c10c391-f56c-4d04-a889-bd1bd8f746f0",
...
_payments: [
...
],
_logs: [
...
],
}
Upd (duplicate from comment for future visitors).
Also, you should pay attention to DTO. I suppose this link will be interesting https://stackoverflow.com/a/36175349/1581741 .
Upd2.
At current moment I treat your collection /customers/id/payments like this:
/payments?user_id=123
where user_id is filtering field on payments table.
I think your problem that you confuse your REST API with your database. They don't have to follow the same structure. You can easily return the whole nested JSON for GET /customers/{id} if that's what you need from your REST API.
I am using symfony 2, I have one field in database "old_status". I want to change it to "status" field (change in both database + entity. I changed it because It is making the developer confuse)
/**
* #var integer
*
* #ORM\Column(name="status", type="smallint", nullable=false, options={"unsigned"=true})
*
* #Expose
*/
private $status;
So in the response it will change to "status". But I don't want to change the contract. I want to show both of "old_status" and "status" fields but with same value. So the current partner will continue use "old_status" until they move to new field. The new partner will use "status".
I don't want add more field in database, I want to handler it by use entity.
Can I do it with entity?
You can simply leave the old getter and make them both new and old methods use the new field name status.
something like:
public function getStatus() {
return $this->status;
}
/**
* #deprecated Renamed to getStatus
*/
public function getOldStatus() {
return $this->status;
}
You can do a similar thing with other methods, like setters, if needed.
Since $status is a private field, it's just a matter of public interface.
So let's say we use a User and a Ticket class. They are normal entities, nothing fancy.
The User class contains this lines:
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
**/
private $tickets;
The Ticket class contains this:
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="tickets")
* #ORM\JoinTable(name="users_to_tickets")
**/
private $collaborateurs;
To get all ticket's a user has I can just call the getTickets() function created by Symfony. As far as good. The Ticket class has a few additional fields like updated which is a DateTime field or status which is an integer. I would like to sort those tickets by status DESC and updated DESC
I know I could just make a function in the repository like findTicketsByUserOrderedByFooBar($user), but I'm wondering if there isn't a better way.
If you always want your tickets to be in that order you can set and orderBy on the association.
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
* #ORM\OrderBy({"status" = "DESC", "updated" = "DESC"})
**/
private $tickets;
You can add an Helper method to your User entity and sort/filter DIRECTLY on the ArrayCollection with doctrine2 criteria. Something like this:
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedByFooBar()
{
$criteria = Criteria::create()
->orderBy(array('foo'=>'DESC','bar' =>'ASC'))
return $this->tickets->matching($criteria);
}
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedBytitle()
{
$criteria = Criteria::create()
->orderBy(array('title'=>'DESC'))
return $this->tickets->matching($criteria);
}
See also this
Hope this help.
Creating a function the way you suggested would be the suggested approach.
I have two classes with a bidirectional relationship: Player and Team Each player must have exactly one team and each team can have many players.
When I use the default form field (select) and I submit 0 (by manually editing the HTML) the form error shows correctly. However if I use a custom form field type and submit 0 there's no form error but an exception:
Catchable Fatal Error: Argument 1 passed to
...\Entity\Player::setTeam() must be an instance of ...\Entity\Team,
null given, called in
...\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php
on line 360 and defined in ...\Entity\Player.php line 58
How do I show a normal form error instead of this exception?
My classes are straighforward (only the relevant parts posted):
Team.php
class Team {
/**
* #ORM\OneToMany(targetEntity="...\Entity\Player", mappedBy="team")
*/
protected $players;
}
Player.php
class Player {
/**
* #var Team
*
* #ORM\ManyToOne(targetEntity="...\Entity\Team", inversedBy="players")
* #ORM\JoinColumn(name="team_id", referencedColumnName="id", nullable=false)
* #Assert\Valid
*/
protected $team;
/**
* Set team
*
* #param Team $team
* #return Player
*/
public function setTeam(Team $team) {
$this->team = $team;
return $this;
}
/**
* Get team
*
* #return Team
*/
public function getTeam() {
return $this->team;
}
}
The reverseTransform function of my DataTransformer looks like this:
public function reverseTransform($value)
{
if(!$value)
{
return $this->multiple ? array() : 0;
}
//... get entity
}
If you have created a custom form field with a custom data transformer, it is your responsibility to validate client datas. To get the generic message (This value is not valid), you need to throw a TransformationFailedException in your data transformer. Then, everything will be handled by the form component.
EDIT: By default majority of the data transformers in the Symfony core, converts a blank value to null. The responsibility of the data transformer is to convert client data to model data and eventually to detect major error like non-acceptable value for a choice list or missing data in case of datetime field, etc. It is not to validate the data which is the resposibility of the validator component. Then, it lets the validator component make the final validation which is often more complex than a simple data transformer. IMHO, you're currently mixing data transfomer and validation concern.
EDIT: Additionally, you need to allow in your domain object what the data transformer return itself (here, the null value).