What is the correct way of configing Eloquent globally? - php

Eloquent has some assumptions regarding db tables.
It uses plural name of class for tables name. I use singular nouns for tables' name.
By default, Eloquent expects created_at and updated_at columns. I use cData and uDate
I use camelCase to name columns not underline_separated names.
I know it possible to use class properties to overwrite those. What is the correct way to config it globally?

Instead of extending the Model class create a new class for example MyModel and setup your attributes there. Then extend MyModel

The best way is to create your own base model that extends Eloquent. Then you can define new values for the timestamp columns and override the getTable() method:
class BaseModel extends Model {
const CREATED_AT = 'cData';
const UPDATED_AT = 'uData';
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) return $this->table;
return str_replace('\\', '', snake_case(class_basename($this)));
}
}
Then, let all your models extend that base model:
class User extends BaseModel

you can do this by editing vendor\framework\src\Illuminate\Database\Eloquent\Model.php file.
/**
* The name of the "created at" column.
*
* #var string
*/
const CREATED_AT = 'cDate';
/**
* The name of the "updated at" column.
*
* #var string
*/
const UPDATED_AT = 'uDate';
/**
* The name of the "deleted at" column.
*
* #var string
*/
const DELETED_AT = 'dDate';
But honestly speaking, editing the core source is not a good idea. So you can do this by
class MyModel extends Model {
/**
* The name of the "created at" column.
*
* #var string
*/
const CREATED_AT = 'cData';
/**
* The name of the "updated at" column.
*
* #var string
*/
const UPDATED_AT = 'uData';
/**
* The name of the "deleted at" column.
*
* #var string
*/
const DELETED_AT = 'dDate';
}
Now write you model extending MyModel instead of original Model.
for example:
class User extends MyModel{
//Yes, if you want to change default table name then you should do this. Shouldn't be global.
protected $table = "user";
}

Related

How to map type when map keys are not in the entity table with Doctrine?

Can the discriminator mapping strings of a Doctrine entity be fetched from a database foreign table field? If not, how to handle inheritance type mapping in such a situation?
Considering a Doctrine 2.7.2 abstract entity Person with concrete entities A and B like:
/**
* #MappedSuperclass
* #Table(name="PERSON")
*/
abstract class Person {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="name",type="string")
*/
protected $name;
/**
* #OneToOne(targetEntity="PersonType")
* #JoinColumn(name="type", referencedColumnName="id")
*/
protected $type;
}
/**
* #Entity
* #Table(name="PERSON")
*/
class A extends Person {}
/**
* #Entity
* #Table(name="PERSON")
*/
class B extends Person {}
A Person has a type bound to a PersonType entity like:
/**
* #Entity
* #Table(name="PERSON_TYPE")
*/
class PersonType {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="code",type="string")
*/
protected $code;
/**
* #Column(name="name",type="string")
*/
protected $name;
}
How to get the right class instantiated by repository query methods like find(), findBy(), findById(), etc?
Database set:
SELECT * FROM PERSON:
id name type
1 John 1
2 Tom 2
SELECT * FROM PERSON_TYPE:
id code name
1 A Type A
2 B Type B
Expected results:
$entityManager->getRepository("A")->findOneById(2); // null
$entityManager->getRepository("B")->findOneById(2); // B object (Tom)
$entityManager->getRepository("A")->findOneById(1); // A object (John)
I can't find how to specify this using a discriminator.
Alternatively I have a working solution implementing a SQLFilter with the inconvenience to have to enable or disable filter.
An alternative solution may be to populate $discr?
(I can of course use the Person::findByType() method but it would be nice to have this feature directly managed by the respective repositories).

Doctrine query one entity in one-to-many unidirectional with join table

I have two entities linked by a one-to-many unidirectional with join table association.
use Doctrine\ORM\Mapping as ORM;
/**
* #Entity(repositoryClass="FooRepository")
*/
class Foo
{
/**
* #var Collection
* #ORM\ManyToMany(targetEntity="Bar")
* #ORM\JoinTable(
* name="foo_bar",
* inverseJoinColumns={#ORM\JoinColumn(unique=true)}
* )
*/
private $bars;
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// [...]
}
/**
* #Entity(repositoryClass="BarRepository")
*/
class Bar
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// [...]
}
I'd like to create a method into my BarRepository class using the a foo id and a bar id which return one or null Bar object.
Actually my class looks like this:
use Doctrine\ORM\EntityRepository;
class BarRepository extends EntityRepository
{
/**
* Finds a single bar.
* #param int $fooId The foo identifier.
* #param int $barId The bar identifier.
* #return Bar|null
*/
public function findOneByFoo($fooId, $barId)
{
$qb = $this->createQueryBuilder('b');
$qb->innerJoin('Foo', 'f', Expr\Join::WITH, 'f.id = :fooId')
->where('b.id = :barId')
->setParameter('fooId', $fooId)
->setParameter('barId', $barId)
->getQuery()->getOneOrNullResult();
}
}
But this always returns a bar object even if the bar id is not associated to the foo object.
OK, thanks to staskrak, I rewrite my "query" like this, and it works fine.
The Foo and Bar entities are the same.
I kept the same base of the query but added an inner join between the Foo->bars property and the Bar entity.
use Doctrine\ORM\EntityRepository;
class BarRepository extends EntityRepository
{
public function findOneByFoo($fooId, $barId)
{
$parameters = [
':fooId' => $fooId,
':barId' => $barId
];
$qb = $this->createQueryBuilder('b');
return $qb
->innerJoin('Foo', 'f', 'WITH', 'f.id = :fooId')
// below, the added inner join
// it makes the link between the Foo->bars property and the Bar entity
->innerJoin('f.bars', 'fb', 'WITH', 'b.id = fb.id')
->where('b.id = :barId')
->setParameters($parameters)
->getQuery()
->getOneOrNullResult();
}
}
First of all! It's not the required but I suggest you always write
the full mapping in annotations.
Let's look at you entities. We can make the next assertion about your
One-To-Many:
One Foo objects can have many Bar objects, but every Bar object refer to
only one and no more Foo object.
For example One person can have a lot of credit cards, but every credit
card belongs to one single person.
Therefore we can write down:
/**
* #Entity(repositoryClass="FooRepository")
*/
class Foo
{
/**
* Unidirectional One-To-Many
* One Foo has many Bar, however Bar has only one Foo
*
* #ORM\ManyToMany(targetEntity="Bar")
* #ORM\JoinTable(
* name="foo_bar_table",
* joinColumns={#JoinColumn(name="foo_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="bar_id", referencedColumnName="id", unique=true)
*/
private $bars;
/**
* Foo constructor
*/
public function __construct()
{
$this->bars = new \Doctrine\Common\Collections\ArrayCollection();
}
Of course you always will have a bar object with id you have typed.
Why? Let's look at your relations(tables).
Foo - this is a table, which has field id.
Bar - this is a table, which has field id.
foo_bar_table - this table has foo_id, bar_id.
When you make the join - you just add to the one table another one.
These tables don't have association between each other. So you wanted
the Bar object - you got it.
You need to get the Bar object from the Bar repository. It will be
better.

Laravel 5 Many to Many - Table name in singular

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.
}
}

Doctrine - Entity loaded without existing associations

I have two entity types: \Model\News and \Model\News\Category.
\Model\News: (without couple of fields)
namespace Model;
/**
* #Entity
* #Table(name="news")
*/
class News extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #ManyToOne(targetEntity="\Model\User", inversedBy="news")
*/
protected $author;
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news")
*/
protected $category;
}
\Model\News\Category:
namespace Model\News;
/**
* #Entity
* #Table(name="news_category")
*/
class Category extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #Column(type="string", length=50, unique=TRUE)
*/
protected $name;
/**
* #OneToMany(targetEntity="\Model\News", mappedBy="category")
*/
protected $news;
/**
* Constructor
*/
public function __construct() {
parent::__construct();
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
}
Table data from \Model\News:
id | category_id | author_id
------------------------------- ...
4 | 1 | NULL
Table data from \Model\News\Category:
id | name
---------------
1 | General
---------------
2 | Other
While I'm loading News type Entity with this particular code and doing dump with \Kint class:
$sId = '4';
$sModel = 'Model\News';
$oObject = $entity_manager->find($sModel, $sId);
d($oObject);
It returns me this:
My question is, why category property from $oObject variable has NULL values despite the fact that the category with id = 1 exists in database?
UPDATE:
After above code, I want to load this category (with ID=1) separately. Same thing. But... when I'm loading a category with other ID (for example, 2) it's loading with no problems:
$oObject2 = $entity_manager->('\Model\News\Category', '2');
I have no idea what to do now...
If you know that the category entity will be accessed almost each time you load news, you might want to eager load it (force doctrine to load it when News is loaded instead of when a Category property is called).
In order to do so, just add the fetch="EAGER" annotation on your association
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news", fetch="EAGER")
*/
protected $category;
So... I finally came to solve the problem thanks to #asm89 from #doctrine IRC chat in freenode server.
Doctrine is creating a proxy class which uses "lazy loading". Data is loaded only when one of getters is used.
So, after using $oObject->getCategory()->getName(), my category object available from $oObject->getCategory() was filled up with proper data.

NULL inserted while Persisting new Entity, Mapping table column to 2 tables. doctrine 2

I need to map the same column to 2 differences tables (lets say normal and extended).
/**
* #var ItemValue
*
* #OneToOne(targetEntity="ItemValue")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $value;
/**
* #var ItemValueExtended
*
* #OneToOne(targetEntity="ItemValueExtended")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $valueExtended;
/**
* #var string $isExtended
*
* #Column(name="is_extended", type="string", nullable=false)
*/
private $isExtended = 'YES';
I have no problem with joining data based on the isExtended attribute using DQL:
"SELECT id,idv FROM ItemData id
JOIN id.value idv WHERE id.isExtended='NO'";
and
"SELECT id,idv FROM ItemData id
JOIN id.valueExtended idv WHERE id.isExtended='YES'";
but when ever I want to persist a new object, NULL is inserted in id_value column ?!!
$oValue = ItemValue();
.
.
$oData = new ItemData();
$oData->setValue($oValue);
.
.
.
$em->persist($oData);
$em->flush();
Any Idea ?
From Doctrine2 documentation:
In the case of bi-directional associations you have to update the
fields on both sides.
One possible solution would be:
$oData = new ItemData();
$oData->setValue($oValue);
$oValue->setData($oData);
but it's tedious. Another better one is set the cascade option on both sides of the one-to-one association:
#OneToOne(targetEntity="ItemValue"), cascade={"persist", "remove"})
This way your code will work. You can choose the appropriate cascade options looking at here.
In the case where both the parent and child entity are new entities (neither have been persisted) a PrePersist lifecycle event on the parent can help:
/**
* ....
*
* #ORM\HasLifecycleCallbacks
*/
class ParentEntity {...
/**
* #ORM\PrePersist()
*/
public function prePersist() {
foreach($this->getChildEntities() as $childEntity) {
$childEntity->setParent($this);
}
}
-
class ChildEntity {
....
This will automatically create the child -> parent relationship upon saving the parent.
In many instances Doctrine will then be able to work out the rest at the SQL level.

Categories