I am working on a older (laravel 5.6) codebase, this codebase has some models with singular table names (not by my design..). When I setup a new pivot relation between my tables "m_sector" and "m_tasklist" Eloquent automatically assumes that the table name of "m_tasklist" is plural; "m_tasklists". I know this is by Laravel's design, therefor I use a manual override defined in the Tasklist model. After changing the $table property to `protected $table = 'potato'; the changes were detected and implemented in the query..
Error message
"debugMessage": "SQLSTATE[42S02]: Base table or view not found: 1146 Table 'database.m_tasklists' doesn't exist (SQL: select count(*) as aggregate from `m_tasklists` where `id` in (1, 2, 3))"
Tasklist Model
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'm_tasklist';
/**
* A task list belongs to many sectors.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function sector(): BelongsToMany
{
return $this->belongsToMany(Sector::class, 'm_sector_m_tasklist', 'm_tasklist_id', 'm_sector_id');
}
Sector Model
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'm_sector';
/**
* A sector belongs to many task lists.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tasklists(): BelongsToMany
{
return $this->belongsToMany(Tasklist::class, 'm_sector_m_tasklist', 'm_sector_id', 'm_tasklist_id');
}
Picture of PhpMyAdmin table names
Can anyone please help me figure this out, It has been breaking my head for a day now.
If anyone would like to know, the key constraints have formed correctly the migrations work and are set up properly. I can add them if it helps.
The issue wasn't with the model. My usecase had a validation rule which checked whether the item existed in m_tasklists instead of m_tasklist
'rules' => ['exists:m_tasklists,id']
instead of the correct
'rules' => ['exists:m_tasklist,id']
I recommend everyone with a similar problem to run
$ php artisan tinker
$ (new Model)->getTable();
Related
I am learning to use Doctrine on Symfony 5.4.
My understanding is that if you have an entity with a nullable ManyToOne relation
/**
* #ORM\Table(name="tblCustomer")
* #ORM\Entity(repositoryClass=CustomerRepository::class)
*/
class Customer implements \JsonSerializable
{
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="customers", fetch="EAGER")
* #ORM\JoinColumn(nullable=true, name="id_user_default", referencedColumnName="id")
*/
private $dev_user;
}
on a insert without specifying something for dev_user relation, Doctrine try to run a query like
INSERT INTO tblCustomer (id_user_default) VALUES (NULL);
it seems that the "missing relation" is always represented with null. Am I right? Or for example I can make Doctrine set 0 for a missing relation, running then the query
INSERT INTO tblCustomer (id_user_default) VALUES (0);
?
I have an eloquent model "Athlete" and there is another table performances. Each athlete has 0 to many performances. And I would like get best performance of each athlete(personal best) or null if the athlete doesnt have any performances yet.
My athlete model:
class Athlete extends Model
{
// I would like to do something like
public $personalBest = max(performances) - the highest perfomance
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'athletes';
/**
* The primary key associated with the table.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* Get the performances for the Athelete post.
*
* #return HasMany
*/
public function performances()
{
return $this->hasMany('App\EloquentModels\Performance', 'athlete_id', "id");
}
}
I would like to get the highest performance of each athlete. Hope it does make sense.
I think it has to be answered somewhere but I had no luck finding it. So sorry if I just failed to find it.
Performances table
id(int) year(int) performance(float)
-------------------------------------
1 2000 257.3
2 2001 227.3
Just to wrap things up. Posting the final raw query which was generated:
select [athletes].[first_name], [athletes].[last_name], MAX(performance) AS personal_best
from [athletes]
left join [performances] on [athletes].[id] = [performances].[athlete_id]
group by [athletes].[id], [athletes].[first_name], [athletes].[last_name]
order by [personal_best] desc
Using withCount should do the job
$athletes= App\Athlete::withCount('performances')->get();
foreach ($athletes as $athlete) {
echo $athlete->performances_count;
}
If you want max performance, you can do something like
$athletes= App\Athlete::all();
foreach ($athletes as $athlete) {
echo $athlete->performances->pluck('performance')->max();
}
Something like
select e.athelete.id, max(perf.performace) as max_performace
from atheletes ath
left join performaces perf on ath.id = perf.athelete_id
group by ath.id, max_performace
may be something like
DB('athletes as ath')::leftJoin('performaces as perf', 'ath.id', 'perf.athelete_id')->select(e.athelete.id, max(perf.performace) as max_performace)->orderBy('max_performace');
You can use order by max_performace, if you need.
I think you can also use simply
echo $athlete->performances->max('performance');
MySQL Tables:
- category
- unit
- category_unit (many to many)
- category_id
- unit_id
Laravel 5 Models:
<?php
class Unit extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'unit';
}
class Category extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'category';
public function units()
{
return $this->morphToMany('App\Unit', 'category_unit'); // Table not in plural.
}
}
Controller Code:
$category = Category::find($id);
var_dump($category->units);
Error:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'database.category_units' doesn't exist (SQL: select `unit`.*, `category_units`.`category_unit_id` as `pivot_category_unit_id`, `category_units`.`unit_id` as `pivot_unit_id` from `unit` inner join `category_units` on `unit`.`id` = `category_units`.`unit_id` where `category_units`.`category_unit_id` = 1 and `category_units`.`category_unit_type` = App\Category)
Laravel 5 is trying to find the table category_unit as the plural category_units. As my database is not new and I already used it in production servers, I cannot change the table name.
How can I do to Laravel 5 use it with singular name?
The problem here is that you are trying to create Many to Many relationship using a polymorphic one.
The morphToMany() method doesn't take the table name as the second argument. I think your case is simpler, just change the relation to belongsToMany()
So your code should be
class Category extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'category';
public function units()
{
return $this->belongsToMany('App\Unit', 'category_unit'); // Table not in plural.
}
}
I'm working with a legacy database (that means no schema changes!) and I need to create a associations between the the Doctrine entities involved. I'll describe the data structure first and then explain what I've tried.
The database has a user table with various other tables also storing user related info. Eg:
siteUser has:
contentId (PK)
firstName
lastName
username
password
...
and siteUser entities have metadata in this system which is along the lines of:
metadataId (PK)
title
description
keywords
createDate
publishDate
contentId
contentTable (discriminator)
...
Almost everything in the database can have Metadata by storing it's PK in the metadata.contentId field and the table name in the metadata.contentTable field. Note that metadata.contentId is not a foreign key, these must have been alien to the DBA as I'm yet to see a single one.
Users on the system can save information they find relevant to them so that they can come back to the system later and don't have to go hunting for the same information again.
This is done with content types called conLink, conVideo, conLeaflet stored as database entities (which have metadata).
For example a conVideo looks like this:
contentId (PK)
embedCode
The way users can store mark this information as being relevant to them is by the system storing it in a link table called userSavedContent:
userSavedContentId (PK)
userId
metadataId
Note that userSavedContent.userId and userSavedContent.metadataId are also not foreign key constraints.
THE APPROACH!
I need to get user's saved content. In SQL this is no problem!
SELECT
metadata.title,
conVideo.embedCode
FROM
userSavedContent
INNER JOIN
metadata ON userSavedContent.metadataId = metadata.metadataId
INNER JOIN
conVideo ON conVideo.contentId = metadata.contentId
WHERE userSavedContent.userId = 193745
AND metadata.contentTable = 'conVideo'
However doing this in Doctrine is more complicated because the value of metadata.contentTable could potentially be any of the conLink, conVideo, conLeaflet entities.
So my application is built using Symfony2 (and Doctrine) and I have models defined for all of the above entities.
In this Metadata is an abstract class with a discriminator on metadata.contentTable:
/**
*
* #ORM\Table(name="metadata")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="contentTable", type="string")
* #ORM\DiscriminatorMap(
* {
* "conLink" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConLinkMetadata",
* "conVideo" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata",
* "siteUser" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\SiteUserMetadata"
* }
* )
*/
abstract class Metadata
The ConVideoMetadata class extends Metadata and adds a content property that associates the ConVideo entity to it:
/**
* #var ContentType $content
*
* #ORM\OneToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\ContentType\ConVideo",
* inversedBy="metadata",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="contentId")
*/
protected $content;
Now the userSavedContent entity has metadata property to associated it to an item of metadata.
/**
* #var Metadata $metadata
*
* #ORM\ManyToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\Metadata",
* inversedBy="userSavedContent"
* )
* #ORM\JoinColumn(name="id", referencedColumnName="metadataId")
*/
protected $metadata;
And finally the siteUser is related to userSavedContent by the following property on it's entity:
/**
* #ORM\OneToMany(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\UserSavedContent",
* mappedBy="siteUser",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="userId")
*/
private $userSavedContentItems;
THE PROBLEM!
In my siteUserRepository class I now need to query for a siteUser and all it's saved content items:
$builder = $this->createQueryBuilder('s')
->select('s', 'm', 'usc', 'uscm', 'uscc')
->innerJoin('s.metadata', 'm')
->leftJoin('s.userSavedContentItems', 'usc')
->leftJoin('usc.metadata', 'uscm')
->leftJoin('uscm.content', 'uscc');
return $builder;
This doesn't work!
"[Semantical Error] Error: Class MyApp\Bundle\DataApiBundle\Entity\Metadata has no association named content"
This makes sense of course since MyApp\Bundle\DataApiBundle\Entity\Metadata doesn't have the content property, it's child MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata is the one with that association. I thought Doctrine would have been able to work this out but apparently not.
So my question is:
Is this approach very wrong? And if not what can I do to make that association/query work?
The fix for this issue was to get Doctrine to eagerly fetch the concrete metadata->content entities. I could declare these explicitly but used Doctrine's MetadataFactory to get the Metadata entity's discriminator for the list of all possible content types.
$metadataFactory = $this->getEntityManager()->getMetadataFactory();
$metadataMetadata = $metadataFactory->getMetadataFor('MyApp\Bundle\DataApiBundle\Entity\Metadata');
foreach ($metadataMetadata->discriminatorMap as $contentEntity) {
$builder->getQuery()
->setFetchMode(
$contentEntity,
'content',
ClassMetadata::FETCH_EAGER
);
}
I have an Item entity that has a ManyToOne relationship to a Category entity. I want them to be joined by a field other than Category's id (in this case, a field called id2). My schema is listed below.
class Item {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity = "Category")
* #ORM\JoinColumn(name = "category_id", referencedColumnName = "id2")
*/
protected $category;
}
class Category {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\Column(name = "id2", type = "string", length = "255", unique = "true")
*/
protected $id2;
When I try saving an Item I get this error:
Notice: Undefined index: id2 in vendor/doctrine/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 511
Sure enough, if I change id2 to id in the JoinColumn annotation, everything works fine, but I need the entities to be connected through id2. Is this possible?
Edit
What I want to achieve is impossible according to the official Doctrine 2 docs.
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading
proxies with the data, which can lead to unexpected results. Doctrine
can for performance reasons not validate the correctness of this
settings at runtime but only through the Validate Schema command.
source: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys
I think Doctrine wants these to be primary keys, from the docs:
name: Column name that holds the foreign key identifier for this relation.
Another thing that jumps out at me from your code sample is category.id2 being type string, I would at least expect it to be an integer, but it may also need to be for #JoinColumn to work properly.
You may be able to get away with just #Index on category.id2 and leave it as a string though; worth a shot anyway.
Just to report. I was able to join non-PKs in Many2One (undirectional) relation, BUT my object can't be loaded the normal way. It must be loaded with DQL like:
SELECT d,u FROM DEntity d
JOIN d.userAccount u
this way I stopped getting error: Missing value for primary key id on ....