X-Cart: shopping cart software

X-Cart forums (https://forum.x-cart.com/index.php)
-   Dev Questions (X-Cart 5) (https://forum.x-cart.com/forumdisplay.php?f=56)
-   -   How to show all products related to an entity in another model (https://forum.x-cart.com/showthread.php?t=77094)

Ed B. 08-11-2019 08:11 AM

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?

cflsystems 08-11-2019 03:50 PM

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.

Ed B. 08-11-2019 11:08 PM

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.

cflsystems 08-12-2019 05:24 AM

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

Ed B. 08-12-2019 11:08 AM

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.

cflsystems 08-12-2019 12:15 PM

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.

Ed B. 08-13-2019 09:51 AM

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.

cflsystems 08-13-2019 03:59 PM

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"

Ed B. 08-14-2019 09:00 AM

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 ?


All times are GMT -8. The time now is 06:03 PM.

Powered by vBulletin Version 3.5.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.