#ORM\OneToMany Is retrieving the virtual deleted entries - php

I have a relation in Doctrine2 #ORM\OneToMany, suposing that i have table school and student, in the entity school i have the #ORM\OneToMany column students,
and i also have a virtual deletion column deleted_at, so every student that has the deleted_at different of null is a deleted student that is supposed not to appear in the column #ORM\OneToMany $students. How can i make this filter?
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Oceano\Entities\Student",
* mappedBy="cartCore",
* cascade={"all"}
* )
*/
private $students;
So, when i call for school students, it is retrieving also the deleted ones.
$schoolObj->getStudents();
Any Solution using annotation or some clean change?

You practically described Laravel's soft deleting feature. So, if you use it, you do not need to do anything and soft deleted students will not appear. You just need to add Illuminate\Database\Eloquent\SoftDeletes trait to the Student model.
If you're using some own functionality, create a local scope in the Student model:
public function scopeNotDeleted($query)
{
return $query->whereNull('deleted_at');
}
And use it:
Student::notDeleted()->get();
Or:
$school->students()->notDeleted()->get();

You can use Criteria filter class in your entity to students collection which are not deleted
protected getStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq('deleted_at', null));
return $this->students->matching($criteria);
}
To get deleted students you could write it like
protected getDeletedStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->neq('deleted_at', null));
return $this->students->matching($criteria);
}
How filter data inside entity object in Symfony 2 and Doctrine

Related

Allow ManyToMany Duplications on Symfony 5 (Doctrine)

I have a problem, it's for a school project and I need to allow duplications of the same relation between two entities on my app using Symfony 5 & Doctrine & postgresql .
I have a basicly a ManyToMany relation between Order and Products, I don't want to add fields for quantity, so I'm looking to count the number of occurences of the a same relation id_order & id_product on my order_product table, but I can't persist more than one same relation between order & product.
I searched and mainly saw people tryng to avoid duplications of the same relation, i'm looking for the exact contrary.
Thx
When using relation with Many on at least one side of the relation, you get Collection on the opposite side. On the collection you can call count() method.
So if you need to calculate quantity of Products in your Order, your Order entity can look like this:
/** #Entity */
class Order
{
...
/**
* #ManyToMany(targetEntity="Product", inversedBy="orders")
* #JoinTable(name="order_product")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function countProducts(): int
{
return $this->products->count();
}
public function countProductsById(int $productId): int
{
return $this->products->filter(static function(Product $product) use ($productId) {
return $product->getId() === $productId;
})->count();
}
...
}
PS: Also be aware that word Order is a reserved word in PostgreSQL. You need to either name your Order entity differently or escape the naming correctly.

How to copy datas from one table into another one with all columns but not id in Laravel?

I use Laravel 6.x and I need to copy datas from one table to another. As usual both of tables has many columns and I'm looking for a solution where I don't depend on column names. In the future maybe columns will be change and I don't want to touch this part of software on every changes.
I want to do something like this:
INSERT INTO product_copys (SELECT * from products);
I want to copy all columns without the id from the products table.
I use Product and ProductCopy models to handle these datas.
Is there any handy solution for this in Laravel, Eloquent?
You may use the following
In your App\Product Model
class Product extends Model
{
protected $hidden = ['id'];
//...
}
Then in your Controller
$copy = Product::all()->toArray();
ProductCopy::insert($copy);
If you need to process a lot (thousands) of Eloquent records, using
the chunk command will allow you to do without eating all of your RAM:
Product::chunk(200, function($products)
{
ProductCopy::insert($products->toArray());
});
https://laravel.com/docs/7.x/eloquent#chunking-results
I found a solution to get columns without id column (thanks to Laravel Tricks). Here is code for the Product model:
/**
* Get all columns of model
*
* #return Array
*/
public static function getTableColumns() {
$model = new Product();
return $model->getConnection()
->getSchemaBuilder()
->getColumnListing($model->getTable());
}
/**
* Return model's columns without given columns
*
* #param Array $without_columns
* #return Array
*/
public static function withoutColumn(Array $without_columns) {
return array_diff(self::getTableColumns(), $without_columns);
}
And here you can use it:
$columns = Product::withoutColumn(['id']);
Now need only run a raw SQL query in the controller:
public function backup() {
$columns = implode(',', Product::withoutColumn(['id']));
DB::statement('INSERT INTO product_copys ('.$columns.', date_of_backup) (SELECT ' .
$columns .', NOW() AS date_of_backup FROM products)');
return Response::HTTP_OK;
}

How to use custom SELECT with JOINs and GROUP BY in Laravel model?

I want to use sophisticated SELECT query with JOINs and GROUP BY in Laravel model.
Сoncretely I want to make a messager in my application. Here is table "messages" with all messages. Now I want to create model called "Dialog". Keep in mind here is no table "dialogs", a dialog is a result of joining and grouping.
Example of query:
SELECT
cl.name AS client_name,
COUNT(m.id) AS messages_count,
MAX(m.created_at) AS last_message,
COUNT(m.id) > SUM(m.viewed_by_client) AS has_new_for_client,
COUNT(m.id) > SUM(m.viewed_by_user) AS has_new_for_user
FROM messages AS m
INNER JOIN clients AS c ON m.client_id = c.id
GROUP BY c.id
Of cource I can use raw SQL queries. But I want to use Eloquent relations later with all its benefits. For example:
$dialog->client->full_name
$dialog->client->order->ordered_items
I had an idea to create a VIEW in database from my query and to use this view as a fake table in the model. But it seems to me not ideal solution.
So, how can I use JOINs and GROUP BY in Eloquent when I do not have a real table for model entities? Or may be some different solutions for my task?
You can have a database table without an Eloquent model but not the other way around. That said, there's no rule against making more than 1 model per table. Not really standard practice though.
I experimented with making a model that would inherit from another model but the boot method didn't work as expected so I dropped it.
I think you could get all the information you take from that query with accessors in your Client model. Since your query has no where clause, a scope is not really necessary but it could also be done with that.
OPTION 1: Accessors
# App\Client
class Client extends Model
{
// Standard Eloquent relationship
public function messages()
{
return $this->hasMany(App\Message::class);
}
// Accessor $client->client_name
public function getClientNameAttribute()
{
return $this->name;
}
// Accessor $client->last_message
public function getLastMessageAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
// use max() method from collection to get the results
return $this->messages->max('created_at');
}
// Accessor $client->has_new_for_client
public function getHasNewForClientAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_client');
}
// Accessor $client->has_new_for_user
public function getHasNewForUserAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_user');
}
}
And then you can access all the properties dynamically
$dialog = Client::withCount('messages')->find($id);
$dialog->client_name;
$dialog->messages_count;
$dialog->has_new_for_client;
$dialog->has_new_for_user;
$dialog->last_message;
However if you're converting $dialog to an array or json format, accessors will be lost unless you append them. In the same way, you can hide the attributes you don't want to show.
This can be done globally for the model
protected $appends = ['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
protected $hidden = ['name'];
or locally for the query
$dialog->setHidden(['name']);
$dialog->setAppends(['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
OPTION 2: Query scopes
# App\Client
class Client extends Model
{
public function scopeDialog($query)
{
$query->select('name as client_name')
->withCount('messages') // the default name will be messages_count
->selectRaw('max(m.created_at) as last_message')
->selectRaw('count(m.id) > sum(m.viewed_by_client) as has_new_for_client')
->selectRaw('count(m.id) > sum(m.viewed_by_user) as has_new_for_user')
->join('messages as m', 'm.client_id', 'clients.id')
->groupBy('clients.id');
}
}
And then just call it like you would any scope Client::dialog()->...
OPTION 3: Just use whatever methods are already available instead of writing more logic
$dialog = Client::with('messages')->find($id);
// client_name
$dialog->name
// messages_count
$dialog->messages->count()
// last_message
$dialog->messages->max('created_at')
// has_new_for_client
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_client'))
// has_new_for_user
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_user'))
Create dialogs table and put 'dialog_id' column into the messages table. Each message has a dialog and a client. Create relationships in each model. So you can access attributes over models as you want. By doing this, this code works;
$dialog->client->full_name
$dialog->client->order->ordered_items
I am trying to detail example about how to get User Model's Accessor in another model with using relationship
Suppose, we have User table & Comment Table...
Now, Suppose I appends User's Profile Full URL in User model using "getProfilePhotoUrlAttribute" Method. when I call User model eloquent then it's appends User Profile Image automatically.
but Now I wants to get that user's profile Full URL in with Comments then we can't access Accessor using Join because with join we can join only out DataBase's Table Columns. If we have profile_photo_path column & doesn't have profile_photo_url named column as we define accessor function name then we can't access using jjoin. in this case we wants to use Relationship method
For example:-
Case :- 1 You wants to Get the user's comments with User details
In this case, User have one or more than one comments So we need to use One TO Many Relation
App/Models/User.php file
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'profile_photo_url',
];
/**
* Get the URL to the user's profile photo.
*
* #return string
*/
public function getProfilePhotoUrlAttribute()
{
... here return full profile URL (concat profile_path with storage/public location path)...
}
/**
* Get the user's comments with User details.
*
* One To Many relation
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
Now then, use Model eloquent Query like below
$user = User::with('comments')->where('id', '=', '2')->get();
echo '<pre>';
print_r($user->toarray());
Case :- 2 You wants to Get the user details of the all comments.
In this case, we need to use Many TO One Relation
App/Models/Comment.php file
/**
* Get the user details of the comments.
*
* One To Many (Inverse) / Belongs To
*/
public function user()
{
return $this->belongsTo(User::class);
}
then use Model eloquent Query like below
$comments = Comment::where('deal_id', '=', '45')->get();
print_r($comments->toarray());
foreach ($comments as $comment) {
print_r($comment->user->toarray());
echo $comment->user->profile_photo_url;
echo "<br/>";
}
NOTE:- I used Latest version - it is Laravel 8, So Syntax may vary as per your Laravel Version
For More Detail with Output Data check here my answer on another question
& you can check it in Laravel Official Documentation

Laravel polymorphic-relationship many to many

I'm having some trouble figuring out the polymorphic relationships.
I've read the documentation but for me it is quite confusing.
Hope anyone has the time to help me a bit to understanding it.
What I'm trying to do is to have a very simple tag system for some wallpapers.
I started a new test project just to get this working.
I have 3 models: Wallpaper, Tag and WallpaperTag
class Wallpaper extends Model
{
protected $primaryKey = 'wallpaper_id';
protected $table = 'wallpapers';
protected $guarded = ['wallpaper_id'];
/**
* Get all the tags assigned to this wallpaper
*/
public function tags()
{
//
}
}
class Tag extends Model
{
protected $primaryKey = 'tag_id';
protected $table = 'tags';
protected $guarded = ['tag_id'];
/**
* Get all wallpapers that have this given tag
*/
public function wallpapers()
{
//
}
}
class WallpaperTag extends Model
{
protected $primaryKey = 'wallpaper_tag_id';
protected $table = 'wallpaper_tags';
protected $guarded = ['wallpaper_tag_id'];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
* Wallpaper relation
*/
public function wallpaper()
{
return $this->belongsTo('App\Wallpaper','wallpaper_id');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
* Tag relation
*/
public function tag()
{
return $this->belongsTo('App\Tag','tag_id');
}
}
The wallpapers table in this test project contains only wallpaper_id
The tags table contanis a tag_id and a tag
The wallpaper_tags table contains a foreign key for both tags.tag_id and wallpapers.wallpaper_id
I've set it up like this so wallpapers can share tags without duplicating them. The problem is that I really dont understand the polymorphic relations and the example in the documentation.
Can anyone here 'spoonfeed' how this would work? :') Thanks in advance for all help.
So you are trying to create a relationship with ManyToMany between 2 tables, which in the DB needs a 3rd table to allow you to create such relationship.
This is due to the fact that one Wallpaper can have many Tag and vice versa! For such you need a 3rd table that holds that information accordingly.
The 3rd table is only holding ids in relationship to your 2 main tables. This allows the flexibility you are looking for, while your Object tables can actually hold information specific to them, without you having to duplicate it.
If you were to store the relationship ids on both tables you would be forced to duplicate your data and that is just something you do not wish on databases! Imagine having to update 1000 rows because it is basically the same wallpaper but with so many different tags.
Anyway, below is the code that should be get you going:
You do need to create a class to represent your relationship table (Kudos on the WallpaperTag class! That is the one!);
You do not touch that class anymore, do not add belongs or any other function!
You create the relationships on the main classes Wallpaper and Tag;
class Wallpaper extends Model
{
...
public function tags()
{
return $this->belongsToMany('App\Tag', 'wallpaper_tag', 'tag_id', 'wallpaper_id');
}
}
class Tag extends Model
{
...
public function wallpapers()
{
return $this->belongsToMany('App\Wallpaper', 'wallpaper_tag', 'wallpaper_id', 'tag_id');
}
}
class WallpaperTag extends Model
{
}
Laravel should create a relationship between your classes and map it accordingly to the correct 3rd table to sort the search for you.
If you follow the semantics all you needed was the class name. If ids are to change, then you will need to start telling Laravel what id column names it should be looking for as you deviate from the normal behaviour. It still finds it, just needs some guidance on the names! Hence why we start adding more parameters to the relationships belongsTo or hasMany etc :)
Pivot Table Migration
You do not need an id for your pivot table since your primary key is a combination of the two foreign keys from the other tables.
$table->bigInteger('wallpaper_id')->unsigned()->nullable();
$table->foreign('wallpaper_id')->references('wallpaper_id')
->on('wallpaper')->onDelete('cascade');
$table->bigInteger('tag_id')->unsigned()->nullable();
$table->foreign('tag_id')->references('tag_id')
->on('tags')->onDelete('cascade');
Let me know if it helped! :3

Doctrine Compund Keys

I have these 3 table
"Business" with these fields: Id, Name, etc..
"City" with these fields: Id, Name, etc..
And then I have a table called BusinessCity (given that a bussines can be related to many cities). This table has the fields "BusinessId" and "CityId".
Im trying to relate the CityId to the City entity, and BusinessId to the business entity, on the class BusinessCity. I've been googling this for the past 3 days and couldnt find an answer, if this has been asked before im sorry i didnt see it. Could anyone help me or give me some pointers on how to get this done. Thanks in advance
What you are trying to achieve is a bi-directional many-to-many relation with a joinTable.
Many businesses can reside in multiple cities and in one city there can be multiple businesses.
In a many-to-many relationship either side can be the owning side. JoinTable definition can be left out and has sensible defaults but if you want to specify it concretely i included it in the example.
Business (in this example: owning side = inversedBy = JoinTable definition)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/City", inversedBy="businesses",cascade="{persist,merge}" fetch="EAGER")
* #ORM\JoinTable(name="BusinessCity",
* joinColumns={#JoinColumn(name="business_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="city_id", referencedColumnName="id")}
* )
*/
protected $cities;
public function __construct()
{
$this->cities = new ArrayCollection();
}
public function getCities()
{
return $this->cities;
}
public function setCities(Collection $cities)
{
// using map with closure to have dublicate/type-checking provided by addCity
$this->cities->map(function($city) {
$this->addCity($city);
});
return $this;
}
public function addCity(CityInterface $city)
{
// ... you don't want dublicates in your collection
if (!$this->cities->contains($city)) {
$this->cities->add($city);
}
return $this;
}
public function removeCity(CityInterface $city)
{
$this->cities->removeElement($city);
return $this;
}
// other properties and methods ..
City (inverse side = mappedBy)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/Business", mappedBy="cities")
*/
protected $businesses;
// getters & setters ...
// other properties and methods ...
This is actually pretty simple, all you have to do is define your JoinTable. It's not easy to find in the docs, but there is an example in the section Composite Primary Keys.
In short, all you have to do is use oneToMany/manyToOne-associations with the class representing your JoinTable instead of directly associating both Business and City with ManyToMany-associations.

Categories