Error in relationships between two tables in laravel - laravel-5.7

I am developing an e-commerce project in which I have the following tables,
1. Products table :
ID (int)
Title (String)
fabric (unsignedInteger)
created_at (timestamp)
I have also used this code below in migration.
$table->foreign('fabric')->references('id')->on('fabrics');
2. Fabric Table
ID (int)
Title (string)
My models are:
class Product extends Model{
public function fabric(){
return $this->hasOne('App\Fabric','id', 'fabric');
}
}
class Fabric extends Model{
public function products(){
return $this->belongsTo('App\Product', 'fabric', 'id');
}
}
I want to get the product fabric in view using this
{{ $product->fabric()->title }}
However, it returns
Object of class Illuminate\Database\Eloquent\Relations\HasOne could
not be converted to string

One cannot use the same name for the property as well as for function as what I am doing there, I am using fabric for both the function as well as for the property.
So I changed my table column from fabric to fabric_id.
It works!

You should either replace {{ $product->fabric()->title }} with {{ $product->fabric->title }}
or
You can replace it with $product->fabric()->get()->title;
What you are doing with your original line of code is trying to print out the relationship as opposed to the object returned.

first of all, I assume that you have defined $fillable array within your fabric Model with title
you should replace
{{ $product->fabric()->title }}
by
#isset($product->fabric)
{{ $product->fabric->title }}
#endisset

Related

Symfony, OneTwoMany, first child element in twig

Product and Image are two entities linked by a oneToMany association (one product has many images). I try to enumerate with TWIG each product with the first image (filename fied) like this :
class ProductRepository extends EntityRepository
{
public function getProductsWithImages() {
$query = $this->createQueryBuilder('e')
->leftJoin('e.images', 'i', 'with', 'i.order = :order')
->setParameter('order' , 0)
->select('e')
->addSelect('i');
return $query->getQuery()->getResult();
}
}
But I got this error :
Method "filename" for object "\entity\product" does not exist.
I understand why (product entity has no image field). What is the best pratice to get only one child element without add a reference on the parent (like a mainImage field) ?
As the doctrine documentation explains :
A one-to-many association has to be bidirectional, unless you are
using an additional join-table. This is necessary, because of the
foreign key in a one-to-many association being defined on the “many”
side. Doctrine needs a many-to-one association that defines the
mapping of this foreign key.
I have this kind of relation in my current project and I simply defined bidirectional one-to-many association. So in your twig view you should be able to do for example :
{# first Image linked to the Product #}
{{ product.images.first }}
The attribute images is an ArrayCollection.
Hope it helps

How to retrieve translation of a "sub entity" using Symfony a2lix knp doctrine behaviors translatable

I'm new in symfo but I need to translate content of my site.
I'm using a2lix (last version) and KNP doctrine behaviors (Translatable).
Let's say that I have 2 entities (e.g. Articles and Categories).
As in the doc (https://github.com/KnpLabs/DoctrineBehaviors) for translations, I'm using 2 classes for Categories (Category and CategoryTranslation).
To retrieve the translations, of my category, I'm using a query with the locale. I get the locale with Request $request ($locale = $request->getLocale();). Here is an example of my controller and the query in my repository.
Controller
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$locale = $request->getLocale();
$entities = $em->getRepository('AcmeBundle:Category')->findAllByLocale($locale);
return $this->render('CTCArtworkBundle:Backend/Artwork:index.html.twig', array(
'entities' => $entities,
));
}
Repository
I'm trying to retrieve informations for the locale.
public function findAllByLocale($locale){
return $this->createQueryBuilder('a')
->join('a.translations', 'aTrans')
->where('aTrans.locale = :locale')
->setParameter("locale", $locale)
->addSelect('aTrans')
->getQuery()
->getResult()
;
}
I don't know if it's a good practice but it works for me. I retrieve fr/en categories in my Twig template like so when I change the url :
<tr>
<th>Category</th>
<td>{{ category.translations|First.name }}</td>
</tr>
My problem
For the translation of my article, I do the same. I have 3 properties
- title
- description
- category (I'm using a2lix_translatedEntity (http://a2lix.fr/bundles/translation-form/#bundle-additional))
When I try to render a record of Article, I never retrieve the translation for my category Name but well for title and description.
I also read that (https://github.com/KnpLabs/DoctrineBehaviors#guess-the-current-locale) but I don't really understand. Is that a way to always pass locale ?
What am I doing wrong ?
I'm blocked and don't find any documentation to resolve my problem. Sorry for my english ;-)
Any help would be very appreciate. Many Thanks
KNP has its own way to guess the current locale, simply by accessing current request scope. The whole "passing locale" thing is useful if you want to pull records for specific locale.
Now, for your category translation. Since you did not include your entities, I will try to show you some examples to access your translations.
In your Category entity, lets say you have a property name that would return your category name. Then you can define a simple helper method that would return that name, by current locale:
public function getName() {
if( $name == $this->translate()->getName() ) {
return $name;
}
return '';
}
So, what have we done here?
$this->translate()->getName() - this line looks for your translation entity (in this case that would be CategoryTranslation) and invokes method getName() . Then, we either return translated category name, or an empty string if no translation has been added.
And lastly, this is how you can access your category name in your twig template:
Since we defined our helper method, there is no longer any need to access .translations in your template. You can simply call:
{{ category.name }}
Hope you got the idea.
And you can also use this
{{ category.translate.name }}
With DoctrineBehaviors v2, you can add this to your Category class:
public function __call($name, $arguments)
{
return $this->proxyCurrentLocaleTranslation($name, $arguments);
}
Here's what it does. So, in your Category entity, lets say you have a property description that would hold your category description. The code above will generate a corresponding property getter: getDescription(). Which ultimately will allow you to use this property in your Twig template:
{{ category.description }}

How can I select count(field) and still get an object with createQueryBuilder

I want to access a query result in twig as a whole object. In the query however, I want to select just several fields from the tables + count. In my example I have a company, which is owned by an owner and has several employees. I want to create a query, which gives me a list of companies ordered by count of their employees.
$qb = $this->createQueryBuilder('c');
$qb->select('count(e.id), partial c.{id, name}, partial o.{id, name}');
$qb->leftJoin('c.employees', 'e');
$qb->innerJoin('c.owner', 'o');
$qb->groupBy('c.id');
The query works fine, however I get an error, when I want to access other objects liked with normaly in the company entity (there is an collection of image entities, and each image entity contains the path to the image). The query does not load the whole company entity with its dependencies... I cannot acces the "firstImage" method, which returns the first image from the collection. I get this error
Key "firstImage" for array with keys "0, 1" does not exist in
MyBundle:List:results.html.twig at line 6
Note that leaving out the "employees count" makes it whole work.
$qb = $this->createQueryBuilder('c');
$qb->select('partial c.{id, name}, partial o.{id, name}');
$qb->leftJoin('c.employees', 'e');
$qb->innerJoin('c.owner', 'o');
$qb->groupBy('c.id');
Your object will be the first element of result array and the count() value will be the second. You can access to object properties and methods in twig by the following way:
{{ object[0].firstImage }}
{{ object[0].name }}
And the count of employees can be accessed as:
{{ object[1] }}
Also you may define the name for your count() value in the query
$qb->select('count(e.id) AS empQty, partial c.{id, name}, partial o.{id, name}');
and then you can access this value in twig as:
{{ object['empQty'] }}

How to use custom repository methods in Twig template?

Assuming I have an entity User and an entity Book and they're both joined by User.bookId = Book.id (this marks a user owns a certain book, relation type oneUserToManyBook).
If I now want to execute a performance friendly fetch with Doctrine's DQL or QueryBuilder for all Books a User has read, what is the best way to implement this in a Symfony2/Doctrine2 webapp, so that I can use them in my User loop in a Twig template?
Twig
{% for user in users %}
{{ user.name|e }}
{% for address in user.getAddressesByUserId(user.getId()) %}
{{ address.city }}
{% endfor %}
{% endfor %}
I see two approaches, but both don't lead to my target:
1st approach
Create a custom repository class BookRepository:
public function getBooksOwnedByUser($user_id) {
return $em->createQuery('SELECT b.title
FROM MyBundle\Entity\User u,
MyBundle\Entity\Book b
WHERE u.book_id = b.id'
AND u.id = :user_id)
->setParameter('user_id', $user_id)
->getResult();
}
Problem: Works fine, but I cant call getBooksOwnedByUser() in my Twig template (because it's not tied to the entity User, but to it's repository, which is a subclass of Doctrine\ORM\EntityRepository.
2nd approach
Execute the same query as above - not in my UserRepository, but directly in my User entity class.
Problem here: I could call this method in my Twig template, but I cannot (and should not) use the EntityManager in my User entity class.
It's best if you make a relationship from User to Books. Assuming you have made this relationship you can make your query like this:
public function getBooksOwnedByUser($user_id) {
return $em->createQuery('SELECT u, b
FROM MyBundle\Entity\User u
JOIN u.books b
WHERE u.id = :user_id')
->setParameter('user_id', $user_id)
->getResult();
}
Then in your controller:
$em = $this->getDoctrine()->getManager();
$user_with_books = $em->getRepository('MyBundle\Entity\User')
->getBooksOwnedByUser($user->getId());
return $this->render('YourTemplate.html.twig', array(
'user_with_books' => $user_with_books,
));
In twig:
{% for book in user.books %}
{{ book.title }}
{% endfor %}
Some considerations:
For multiple users you will have to change the query (lazy loading is possible but not advised).
If it's a lot of data you can get a performance boost by getting a scalar result (Array)
If you need different queries for the user that can not be combined you will have to store different variables (objects or arrays). That's why I named it "user_with_books". But if you only have this user in your template you can just as well call it "user".
user.getAddressesByUserId(user.getId()) <-- passing data from one model to query is the responsiblity of the controller (or a service). Best practice is to avoid doing this in your template.
So the answer:
You can not do anything with a custom repository method because it's a function. A function on itself doesn't represent any data. So this is a way you can retrieve the actual data with that function and display that.

Querying a collection within an entity

I currently have a simple one to many relationship between products and multiple deals (a table of 1 million deals in total) associated with the products.
What I'm trying to do is loop through the top 10 products and select the top deals relating to the product.
What would be the best way to achieve this in Doctrine 2? I was contemplating adding a method such as getTopDeals within the product entity, and then calling it within twig as I looped through each product like so:
{% for product in popular_products %}
{% set deal = product.getTopDeal() %}
{{ product.title }} - {{ deal.title }}, {{deal.price }}
{% endfor %}
However, I've read that generally it is frowned upon adding methods such as this into models, so I'm at an end as to what the best way to do this is.
Make a method in your Deals repository to accept a parameter and return the topdeal. In your controller, array_map() your products to produce an array of deals keyed by product. Then pass the deals array along with your products array to your template.
edit: sample requested:
Repository:
public function getTopDealProduct($productid)
{
$em=$this->getEntityManager();
$qb = $em->getRepository('Bundle:Deal')->createQueryBuilder('d');
$qb->join('d.product', 'p');
$qb->setMaxResults(1);
$qb->addOrderBy('d.price');
$query = $qb->getQuery();
$results = $query->getResult();
return $results;
}
Controller:
public function s2JUsorAction(Request $request, $id)
{
$dealrep = $this->em->getRepository('Bundle:Deal');
$prodrep = $this->em->getRepository('Bundle:Product');
$products= $prodrep->getProducts(); // Not shown here, write this
$deals= array_map(function($element) use ($dealrep){
return $dealrep->getTopDealProduct($element->getId());
}
,$products);
return $this->render('Bundle:Product:Deal.html.twig', array(
'products' => $products
,'deals' => $deals
));
}
The best practice is, "fat models, thin controllers". The logic for selecting the top deals for a product definitely has a place on the model, if the model itself is capable of doing this filtering, eg. it only needs the deal objects, which it has a relation with. For this, you could use the Criteria API, something like:
use Doctrine\Common\Collections\Criteria;
class Product {
private $deals; // many-to-many to Products
public function getTopDeals() {
$criteria = Criteria::create()->orderBy(array('price', 'DESC'))->setMaxResults(10);
return $this->deals->matching($criteria);
}
}
If the selection logic is more complicated, and needs to reach into the entity manager, then it is better suited for placing on an EntityRepository.

Resources