Follow us on Twitter X-Cart on Facebook Wiki
Shopping cart software Solutions for online shops and malls

How to show all products related to an entity in another model
 
Reply
   X-Cart forums > X-Cart 5 > Modifying the design and features (X-Cart 5)
 
Thread Tools Search this Thread
  #1  
Old 08-11-2019, 08:11 AM
 
Ed B. Ed B. is online now
 

eXpert
  
Join Date: Apr 2016
Posts: 221
 

Default How to show all products related to an entity in another model

First of all, here is what I would like to achieve. The module I am working on is for X-cart 5.3.6.0. We sell books, and we would like
to have a page of an author, where we have the name, photo, description of the author
(this part has been done), and all his books underneath. As I wrote in the thread,

https://forum.x-cart.com/showthread.php?t=77082 I made a model for entities "Author" in ManyToMany relationship with the model Product.


I have following files to output the page


Controller file Controller/Customer/Author.php with
Code:
class Author extends \XLite\Controller\Customer\ACustomer { protected $params = array('target', 'author_id'); public function getAuthorId() { return \XLite\Core\Request::getInstance()->author_id ?: 0; } }
so that the page can be called with author_id,
page viewer file View/Page/Customer/Author.php (basically a modification of

View/Page/Customer/Category.php )

Code:
<?php namespace XLite\Module\EdB\Librairie\View\Page\Customer; use \XLite\Core\Database; use \Model\Catalog\Base; /** * Category widget * * @ListChild (list="center", zone="customer") */ class Author extends \XLite\View\AView { /** * Return list of targets allowed for this widget * * @return array */ public static function getAllowedTargets() { $result = parent::getAllowedTargets(); $result[] = 'author'; return $result; } public function findAuthor() { $author_id = $this->getAuthorId(); $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Model\Au thor')->find($author_id); return $author; } public function describeAuthor() { $author = $this->findAuthor(); $value = $author->getDescription(); $value = \XLite\Model\Base\Catalog::getPreprocessedValue($value); echo $value; } protected function getDefaultTemplate() { return 'modules/EdB/Librairie/aut1.twig'; } }
with the twig file skins/customer/modules/EdB/Librairie/aut1.twig
Code:
{{widget('\\XLite\\View\\Image', image=this.findAuthor().getImage(), className=' photo product-thumbnail', verticalAlign='top', id='product_image_' ~ this.findAu thor().author_id, maxWidth=600, maxHeight=600, alt='') }} {{this.findAuthor().name}} <br> <br> {{this.describeAuthor()}}

This part is working. Now, to output the list of books by the author, I have another widget Librairie/View/ItemsList/Product/Customer/BooksBy.php with the following
content (below, I removed the part that "should" define which products to show, so
with this we get all products).

Code:
<?php namespace XLite\Module\EdB\Librairie\View\ItemsList\Product\Customer; /** * * @ListChild (list="center.bottom", zone="customer", weight="300") */ class BooksBy extends \XLite\View\ItemsList\Product\Customer\ACustomer { protected $BooksbyAuthor = null; protected static function getWidgetTarget() { return 'author'; } public static function getAllowedTargets() { $result = parent::getAllowedTargets(); $result[] = self::getWidgetTarget(); return $result; } protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { return \XLite\Core\Database::getRepo('\XLite\Model\Product')->search( $cnd, $countOnly); } protected function getPagerClass() { return 'XLite\Module\EdB\Librairie\View\Pager\Customer\Product\Product'; } }


Of course, I could join the xc_products table with xc_product_author_links table and create a query condition, write a function in Repo class, and this might take less time to finish writing up. However, as I already have pulled the author entity with all properties, including getProducts() which gives an array of products, I would rather use this array to "feed" the getData function.



Now, I encounter two problems.
  1. How to pass an array or even a function from viewer widget to the itemlists widget?
  2. How to transform an array to the return of the getData function?
For the first point, most of my trials ended up with " Using $this when not in object context" or "call to undefined function", so for now I have reproduced the function in ItemsList file, that is

Code:
public function findBooks() { $author_id = \XLite\Core\Request::getInstance()->author_id; $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Model\Au thor')->find($author_id); $products = $author->getProducts(); return $products; }
This is already in Model class and Viewer class, so I really would like to remove these lines, but for now, it works. As to the second issue, the closest thing
I have seen is
https://forum.x-cart.com/showthread.php?t=70741


so I tried the following (among others)
Code:
protected $BooksbyAuthor = null; protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { if(!isset($this->BooksbyAuthor)) { $this->BooksbyAuthor = $this->findBooks(); } return true == $countOnly ?count($this->BooksbyAuthor) : $this->BooksbyAuthor; }
but this leads to
Code:
ERROR: "0" (code N/A) Argument 2 passed to XLite\Core\Model\EntityVersion\BulkEntityVersionFetcher::__construct() must be of the type array, null given, called in /srv/http/newbtq72/xcart-53/var/run/classes/XLite/View/ItemsList/Product/Customer/ACustomerAbstract.php on line 918


and in the log, I find,
Code:
XLite [warning] Warning: array_map(): Argument #2 should be an array in /srv/http/newbtq72/xcart-53/var/run/classes/XLite/View/ItemsList/Product/Customer/ACustomerAbstract.php on line 916



So, would anyone have any advice on this?
__________________
X-cart 5.2.12, php 5.6
Ed from Grenoble, France

Last edited by Ed B. : 08-11-2019 at 09:11 AM. Reason: Corrected a dead link as well as gave the version number.
Reply With Quote
  #2  
Old 08-11-2019, 03:50 PM
  cflsystems's Avatar 
cflsystems cflsystems is offline
 

Veteran
  
Join Date: Apr 2007
Posts: 13,597
 

Default Re: How to show all products related to an entity in another model

Quote:
Originally Posted by Ed B.
... I made a model for entities "Author" in ManyToMany relationship with the model Product.

First of all why are you making Many To Many relationship? If you do this it means one book can be written by more than one author. I mean there are cases when a book has multiple co-writers but they are usually treated as team?

I would change that to One Book One Writer and One Writer Many Books relationship.

The you can easily get Author/Books by using Book->getAuthor() and Author->getBooks()

Even with Many To Many connection you still use Entity->getBooks() and Entity->getAuthors()

There is no need of any fancy functions.
__________________
Steve Stoyanov
CFLSystems.com
Web Development
Reply With Quote
  #3  
Old 08-11-2019, 11:08 PM
 
Ed B. Ed B. is online now
 

eXpert
  
Join Date: Apr 2016
Posts: 221
 

Default Re: How to show all products related to an entity in another model

Quote:
Originally Posted by cflsystems
First of all why are you making Many To Many relationship? If you do this it means one book can be written by more than one author. I mean there are cases when a book has multiple co-writers but they are usually treated as team?
Some authors may write alone and collaborate with others, and for statistic
reasons (and others), we have made this choice.




Quote:


I would change that to One Book One Writer and One Writer Many Books relationship.

The you can easily get Author/Books by using Book->getAuthor() and Author->getBooks()

Even with Many To Many connection you still use Entity->getBooks() and Entity->getAuthors()

There is no need of any fancy functions.




Author->getBooks() is actually working (as long as I define the method for each widget class!), my problem is to use the output of this to "feed" getData(). For example, if I do
Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { $this->getProducts(); }
I get "call to undefined function", and if I do

Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { PathToMyViewerWidget::getProducts(); }
then I get "use of $this not in object context" error.
__________________
X-cart 5.2.12, php 5.6
Ed from Grenoble, France
Reply With Quote
  #4  
Old 08-12-2019, 05:24 AM
  cflsystems's Avatar 
cflsystems cflsystems is offline
 

Veteran
  
Join Date: Apr 2007
Posts: 13,597
 

Default Re: How to show all products related to an entity in another model

getData() works off of the request so you need to look at what your request is and what getData() gives you.
Use the Doctrine dump to check the data if you are not sure. You should have Author if you want to get the books associated with the entity.
So you can do like

Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { $data = parent::getData($cnd, $countOnly); // check if $data has author id - it should have something to relate to what you are trying to get // then you can use it $author = Database::getRepo('\XLite\Model\Product')->find($data-authoutId); $books = $author->getProducts(); // of course make sure you have the id in the request and it is called authorId }

If you want to use
Code:
$this->getProducts()
you need to define getProducts() within this class - since you are getting error on that call it means it is not defined in the class or any of its parents
__________________
Steve Stoyanov
CFLSystems.com
Web Development
Reply With Quote

The following user thanks cflsystems for this useful post:
Ed B. (08-13-2019)
  #5  
Old 08-12-2019, 11:08 AM
 
Ed B. Ed B. is online now
 

eXpert
  
Join Date: Apr 2016
Posts: 221
 

Default Re: How to show all products related to an entity in another model

Thank you very much, and sorry for not understanding all the subtilities of object-oriented PHP, but I am a little bit confused.


Is your code meant to be used "as is" with some "obvious" modification, or is it some sort of "meta code" where I am supposed to write some query in place of

find($data-author_id)? (So probably we need findBy instead of find?) And, should the return of getData $data, $books, or something else?


If I write
Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { $data = parent::getData($cnd, $countOnly); $author_id = \XLite\Core\Request::getInstance()->author_id; $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Model\Au thor')->find($author_id); $products = $author->getProducts(); return $products;

(dump shows that $products give all books by the author) this will give

Code:
ERROR: "0" (code N/A) Argument 2 passed to XLite\Core\Model\EntityVersion\BulkEntityVersionFetcher::__construct() must be of the type array, null given, called in /srv/http/newbtq72/xcart-53/var/run/classes/XLite/View/ItemsList/Product/Customer/ACustomerAbstract.php on line 918
The result of dump on $data, as is, is

Code:
int(46) int(46)


Basically all I want to do is :
  • get the author_id from the cotroller
  • get the books by the author using getProducts()
  • feed the results into getData so that we can output as itemsList
and, of course, this can be done, for example, in a sql query

Code:
select * from `xc_products` join xc_product_author_links on xc_products.product_id=xc_product_author_links.product_id where author_id=$author_id
and presumably this can be translated into findBy() syntax without too much pains. (Then again, I am not completely sure how to transform the query syntax for findBy to the syntax for search() condition, but this is another issue).



However, since I have already pulled all properties of the author (including Products), I have a feeling that it should be more "economic" to feed the results of the query already made, instead of making another query.
__________________
X-cart 5.2.12, php 5.6
Ed from Grenoble, France
Reply With Quote
  #6  
Old 08-12-2019, 12:15 PM
  cflsystems's Avatar 
cflsystems cflsystems is offline
 

Veteran
  
Join Date: Apr 2007
Posts: 13,597
 

Default Re: How to show all products related to an entity in another model

I don't really know what you are doing or how or what your full module looks like. So the code is intended to give you a hint and not to be used directly. You probably have to modify it to fit your module.

For example I keep saying "books" as you call them that but then in your code you use "products" - so obviously you have to modify this snippet of code to fit your module and needs.

The "find" method on the entity will search by the table primary id field (whatever that is) so there is no need to use "findby".

If the $products variable after you do the "find" gives you all the products as expected then the following errors are somewhere else in your code. However - you are trying to change what getData returns and this may also change the type of data being returned. So this could be the reason you are seeing the error.

Best is to modify the $cnd variable and then call the parent getData with the new conditions.

These are just fragments of code you are posting so it is really hard to give you exact code. But it does sound like if you apply author condition to it will give you what you need. So you may just need to add this to the $cnd variable in getData. Again - you will need to know what is already there, what exactly you are passing, etc.
If your author field in the products table is not author but authors (since you have Many To Many relationship you may need to pass a collection of authors even if it is just one.
__________________
Steve Stoyanov
CFLSystems.com
Web Development
Reply With Quote

The following user thanks cflsystems for this useful post:
Ed B. (08-13-2019)
  #7  
Old 08-13-2019, 09:51 AM
 
Ed B. Ed B. is online now
 

eXpert
  
Join Date: Apr 2016
Posts: 221
 

Default Re: How to show all products related to an entity in another model

Thank you so much for your patience.


Quote:
Originally Posted by cflsystems


If the $products variable after you do the "find" gives you all the products as expected then the following errors are somewhere else in your code. However - you are trying to change what getData returns and this may also change the type of data being returned. So this could be the reason you are seeing the error.


As a matter of fact,
Code:
$author->getProducts
and the following give exactly same set of products, but somehow they don't contain same informations.
Code:
public function findBooksBy($author_id) { $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Model\Au thor')->find($author_id); $products = $author->getProducts(); $products = \XLite\Core\Database::getRepo('\XLite\Model\Product') ->createQueryBuilder('avs')->linkInner('avs.authors','l') ->andWhere('l.author_id = :aid') ->setParameter('aid',$author_id) ->getResult(); return $products; }


Now I have the above code in Model/Repo/Product.php, and the part of

View/ItemsList/Products/Customer/BooksBy.php looks like
Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { if(!isset($this->BooksbyAuthor)) { $author_id = \XLite\Core\Request::getInstance()->author_id; $this->BooksbyAuthor = \XLite\Core\Database::getRepo('XLite\Model\Produc t')->findBooksBy($author_id); } return true == $countOnly ? count($this->BooksbyAuthor) : $this->BooksbyAuthor; }
and I get the correct ItemsList.


I am not very happy with this solution for two reasons: basically here I am defining a
variant of getProducts() from scratch that will fit in getData() without using what I already have

in getProducts().


And the second is

Quote:
Originally Posted by cflsystems
Best is to modify the $cnd variable and then call the parent getData with the new conditions.


I believe you are right, but somehow having gone through the dev doc, I have been unable to find the syntax for $cnd in search()







Quote:
Originally Posted by cflsystems
These are just fragments of code you are posting so it is really hard to give you exact code.

Now I have realized that in my first post Model/Author.php is not complete and Model/Product.php is missing. Model/Author.php is attached in the other thread https://forum.x-cart.com/attachment.php?attachmentid=5352&d=1565023268

and the relevant portion of Model/Product.php looks like
Code:
/** * Authors * * @var \Doctrine\Common\Collections\ArrayCollection * * @ManyToMany (targetEntity="XLite\Module\EdB\Librairie\Model\Author", inve rsedBy="products") * @JoinTable (name="product_author_links", * joinColumns={@JoinColumn (name="product_id", referencedColumnName="p roduct_id", onDelete="CASCADE")}, * inverseJoinColumns={@JoinColumn (name="author_id", referencedColumnN ame="author_id", onDelete="CASCADE")} * ) */ protected $authors;
(I know, I am not quoting the entire code, but the class contains whole bunch of functions which are unrelated to the problem we discuss here and the file is quite large). I also have classes related to the images, editing author entities in admin area etc, but these shouldn't really affect my current problem.




Quote:
Originally Posted by cflsystems
But it does sound like if you apply author condition to it will give you what you need. So you may just need to add this to the $cnd variable in getData. Again - you will need to know what is already there, what exactly you are passing, etc.
If your author field in the products table is not author but authors (since you have Many To Many relationship you may need to pass a collection of authors even if it is just one.




The relationship is Many To Many, so in the products table there is no such field as author or authors, instead there is product_author_link table, consisting of id, product_id and author_id.
__________________
X-cart 5.2.12, php 5.6
Ed from Grenoble, France
Reply With Quote
  #8  
Old 08-13-2019, 03:59 PM
  cflsystems's Avatar 
cflsystems cflsystems is offline
 

Veteran
  
Join Date: Apr 2007
Posts: 13,597
 

Default Re: How to show all products related to an entity in another model

https://devs.x-cart.com/getting_started/working-with-database.html
search in the page for "// defining condition object"
__________________
Steve Stoyanov
CFLSystems.com
Web Development
Reply With Quote
  #9  
Old 08-14-2019, 09:00 AM
 
Ed B. Ed B. is online now
 

eXpert
  
Join Date: Apr 2016
Posts: 221
 

Default Re: How to show all products related to an entity in another model

Thank you for the reference. Unfortunately the explanation doesn't go far enough to cover my situation.



Since my code in last post
Code:
public function findBooksBy($author_id) { $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Model\Au thor')->find($author_id); $products = $author->getProducts(); $products = \XLite\Core\Database::getRepo('\XLite\Model\Product') ->createQueryBuilder('avs')->linkInner('avs.authors','l') ->andWhere('l.author_id = :aid') ->setParameter('aid',$author_id) ->getResult(); return $products; }
does the job, one would think this can be translated easily to $cnd object
something like
Code:
prepareCndAuthoris(\Doctrine\ORM\QueryBuilder $queryBuild er, $value) { $result = $queryBuilder; $result ->linkInner('p.authors') ->andWhere('p.authors.author_id = :aid') ->setParameter('aid',$value); return $result; }
but this gives an error (p.authors has no field or association called author_id).
As a matter of fact Product->getAuthors give an array of Authors. Using
p.authors.0.author_id (obviously this would only get the first author if it worked)
doesn't work either.


I thought I could do something like
Code:
protected function getData(\XLite\Core\CommonCell $cnd, $countOnly = false) { $author_id = \XLite\Core\Request::getInstance()->author_id; $author = \XLite\Core\Database::getRepo('XLite\Module\EdB\Librairie\Mod el\Author')->find($author_id); $products = $author->getProducts(); $value=""; foreach ($products as $product) { $pid = $product->product_id; if($value == "") { $value = "p.product_id =$pid"; } else $value = " or p.product_id =$pid"; } $cnd = new \XLite\Core\CommonCell(); $cnd = Test->$value; return(\XLite\Core\Database::getRepo('\XLite\Model\Product')->search( $cnd, $countOnly); }
in the viewer class with
Code:
protected function prepareCndTest(\Doctrine\ORM\QueryBuilder $queryBuild er, $value) { $result = $queryBuilder; $result->andWhere('$value'); return $result; }
in repository class. Basically I get what I need as $value from the viewer class this way, but somehow it seems that I can't pass a string as variable in this way, and this leads to an error.


So, would there really be a "clean" way to do this by playing with $cnd ?
__________________
X-cart 5.2.12, php 5.6
Ed from Grenoble, France
Reply With Quote
Reply
   X-Cart forums > X-Cart 5 > Modifying the design and features (X-Cart 5)


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump


All times are GMT -8. The time now is 09:04 PM.

   

 
X-Cart forums © 2001-2018