I would like to build a query with clause "OR" and the query receive an array parameter and doesn't know how elements it contains.
The query has to return a list of contents that have the category "1" or "2"(if $categ array contain [1,2]) etc.
Category is a foreign key in the content entity.
My repository:
public function getContentOfSomeCategories($categs)
{
$query = $this->createQueryBuilder('c');
$conditions = [];
foreach($categs as $value){
$conditions[] = 'c.contentCategory = '.$value;
}
$orX = $query->expr()->orX();
foreach ($conditions as $condition) {
$orX->add($condition);
}
$query->add('where', $orX);
I obtain the error message "Too many parameters: the query defines 1 parameters and you bound 2".
From PHP 5.6 onward, you can probably use PHP splat ... to expand an array to arguments.
$query = $this->createQueryBuilder('c');
$expr = $query->expr();
$conditions = [];
$valueNo = 0;
foreach ($categs as $value) {
$conditions[] = $expr->eq('c.contentCategory', 'value'.$valueNo);
$query->setParameter('value'.$valueNo, $value);
$valueNo++;
}
$query->andWhere($expr->orX(...$conditions));
Disclaimer: I've not tested this.
See: https://www.php.net/manual/en/functions.arguments.php
You can also use ... when calling functions to unpack an array or Traversable variable or literal into the argument list:
Edit:
If your query is simple, you could just use orWhere instead.
$query = $this->createQueryBuilder('c');
$expr = $query->expr();
$valueNo = 0;
foreach ($categs as $value) {
$query->orWhere($expr->eq('c.contentCategory', 'value'.$valueNo));
$query->setParameter('value'.$valueNo, $value);
$valueNo++;
}
Edit 2:
You might even want to just use an in clause instead.
$query->where($expr->in('c.contentCategory', ':values'));
$query->setParameter('values', $categs);`
You can achieve this quite easily, below utilises the query builder expression builder:
$queryBuilder = $this->createQueryBuilder('c');
$conditions = [];
$i = 0;
foreach ($categs as $value) {
$i++;
$conditions[] = $queryBuilder->expr()->eq('c.contentCategory', $i);
$queryBuilder->setParameter($i, $value);
}
$queryBuilder->andWhere(
$queryBuilder->expr()->orX()->addMultiple($conditions)
);
If you parameter applies to all then you can just add
$queryBuilder->setParameter();
Related
I'm working on a symfony project entity with query builder. When I try to run this function I get this issue.
Invalid parameter number: number of bound variables does not match number of tokens
public function json_filterAllproductsAction() {
$search = "";
$category = 1;
//Combine tables and create the query with querybuilder
$em = $this->container->get('doctrine.orm.entity_manager');
$qb = $em->createQueryBuilder();
$qb->select('p')
->from('EagleAdminBundle:Products', 'p')
->orderBy('p.id', 'DESC');
if ($category != 0) {
$qb->where($qb->expr()->in('p.category', '?1'))
->setParameter(1, $category);
}
$qb->where('p.productTitle LIKE :title')
->setParameter('title', "$search%");
//convert to json using "JMSSerializerBundle"
$serializer = $this->container->get('serializer');
$jsonproducts = $serializer->serialize($qb->getQuery()->getResult(), 'json');
return new Response($jsonproducts);
}
I think error is in
$qb->where($qb->expr()->in('p.category', '?1'))
->setParameter(1, $category);
It would be great help someone can help me.
You have two issues here. The first is that your last where clause overwrites the first one. This can be fixed by using andWhere. The second is that your mixing named parameters (:title) with positional parameters (?1). Mixing is a no no. And you don't really need the expr object. Try:
$qb->select('product')
->from('EagleAdminBundle:Products', 'product')
->orderBy('product.id', 'DESC');
if ($category) {
$qb->andWhere('product.category IN (:category)');
$qb->setParameter('category', $category);
}
$qb->andWhere('product.productTitle LIKE :title');
$qb->setParameter('title', "$search%");
I'm using a search code and pagination code in my controller(s), it goes without saying it's bad coding habits by repeating code. That being said what is the best practice in Symfony2, to avoid repeating code in all my controllers?
And how do I access the code once it's been re-factored?
Controller
// Search code
$results = null;
$query = $request->query->get('q');
if (!empty($query)) {
$em = $this->getDoctrine()->getManager();
$results = $em->createQueryBuilder()
->from('AcmeDemoBundle:Blog', 'b')
->select('b')
->where('b.title LIKE :search')
->setParameter(':search', "%${query}%")
->getQuery()
->getResult();
}
// Pagination code
$page = $request->get('page');
$count_per_page = 5;
$total_count = $this->getTotalBlogs();
$total_pages = ceil($total_count/$count_per_page);
if (!is_numeric($page)) {
$page = 1;
} else {
$page = floor($page);
}
if ($total_count <= $count_per_page) {
$page = 1;
}
if (($page * $count_per_page) > $total_count) {
$page = $total_pages;
}
$offset = 0;
if ($page > 1) {
$offset = $count_per_page * ($page - 1);
}
$em = $this->getDoctrine()->getManager();
$blogQuery = $em->createQueryBuilder()
->select('b')
->from('AcmeDemoBundle:Blog', 'b')
->addOrderBy('b.created', 'DESC')
->setFirstResult($offset)
->setMaxResults($count_per_page);
$blogFinalQuery = $blogQuery->getQuery();
$blogPage = $blogFinalQuery->getArrayResult();
foreach ($blogPage as $blog) {
$blog_id = $blog['id'];
$commentRepository = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Comment');
$comments[] = $commentRepository->findByBlog($blog_id);
}
// exit(\Doctrine\Common\Util\Debug::dump($comments));
return $this->render('AcmeDemoBundlBundle:Default:index.html.twig', array(
'blogPage' => $blogPage,
'total_pages' => $total_pages,
'current_page' => $page,
'comments' => $comments,
'query' => $query,
'results' => $results,
));
For a start, you can put all your custom queries in custom repository classes. I suspect that would cover all the re-use you need in this case.
For example, create a BlogRepository class in AcmeDemoBundle:Repository and annotate the Blog entity class as follows to define it's repository class:
/**
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Repository\BlogRepository")
*/
Then add methods to the repository for each custom query that's needed, bearing in mind the way that repository methods are typically named. It looks as though the controller method getTotalBlogs() could also be a method on BogRepository e.g.:
public function findFiltered($page, $count_per_page = 5, $filterText = '')
{
$total_count = $this->findTotal();
// Code to initialise page and offset here
$queryBuilder = $this->createQueryBuilder('blog');
$queryBuilder->...
...
}
Note that the above method could be used to get all the blogs if no $filterText is passed in. You would just need something like this:
if (!empty($filterText))
{
queryBuilder->where('b.title LIKE :search')
->setParameter(':search', "%${query}%")
}
Then, a CommentRepository could be created with a method to find all the comments for a given set of blog(id)s. Note you could use a sql 'IN' clause to get all the comments using a single query:
$commentQuery = $em->createQueryBuilder()
->select('comment')
->from('AcmeDemoBundle:Comment', 'comment')
->where('comment.blog IN (:ids)')
->setParameter('ids', $blogIds);
In addition to custom repository classes I use manager services (e.g. BlogManager) to encapsulate business processes. My controllers mainly use the managers rather than using the repositories directly but it depends on the functionality.
I'm a little confused that you have an overall results query which only returns Blogs where the title is like the search text whilst the paged query returns (a page of) all blogs. That may just be a because your code is in progress?
I have an Object that extends Page ("Thing") which has a many_many relationship with a DataObject ("Tag").
class Thing extends Page
{
static $many_many = array(
'Tags' => 'Tag'
);
}
class Tag extends DataObject
{
static $belongs_many_many = array(
'Things' => 'Thing'
);
}
I have an array of Tag IDs and I want to get a list of Things that have all of these tags attached.
The following should be possible...
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$things->filter('Tags.ID', array($tag_ids));
...but this just returns an unfiltered list. Apparently this hasn't been implemented for relationships yet. So how can I do it?
I think if you are using older versions for SilverStripe 3, you need to use the ExactMatchMulti SearchFilter.
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$things->filter('Tags.ID:ExactMatchMulti', $tag_ids);
I don't see an easy solution do do this directly with the ORM. But you should be able to solve this with some loops and filtering.
$tag_ids = array(1,2,3,4);
$things = Thing::get();
$results = new ArrayList();
$tags_count = count($tag_ids);
$matches = 0;
foreach ($things as $thing)
{
foreach ($thing->Tags() as $tags)
{
$matches = 0;
foreach ($tags as $tag)
{
if ( in_array($tag->ID, $tag_ids) )
{
$matches++;
}
}
if ( $matches === $tags_count )
{
$results->push($thing);
}
}
}
Although not tested, this should leave you with $things that contain those with all tags and more. (Assuming a thing can only be tagged once with the same tag).
For performance reasons you would probably be better off relying as much as possible on a sql query.
$tag_ids = array(1,2,3);
$objects = new ArrayList();
if (count($tag_ids)) {
$sql = "SELECT \"SiteTree\".*, \"Page\".*, \"Thing\".*,count(\"ThingID\") AS ThingIDCount FROM \"SiteTree\" ";
$sql.= "LEFT JOIN \"Page\" ON \"Page\".\"ID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Thing\" ON \"Thing\".\"ID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Thing_Tags\" ON \"Thing_Tags\".\"ThingID\" = \"SiteTree\".\"ID\" ";
$sql.= "LEFT JOIN \"Tag\" ON \"Thing_Tags\".\"TagID\" = \"Tag\".\"ID\" ";
$sql.= "WHERE \"TagID\" IN (" . implode(',', $tag_ids) . ") GROUP BY \"ThingID\" HAVING ThingIDCount >= " . count($tag_ids);
// Get records
$records = DB::query($sql);
foreach($records as $record) {
$objects->push(new Thing($record));
}
}
// Display the Things for demo purposes
foreach($objects as $thing){
Debug::Dump($thing);
}
NB I have added a left join to a Thing table, as I imagine you have some db fields on it, but drop the line (and the \"Thing\".* on the SELECT statement) if that's not the case
You can try to do the following:
Tag::get()->byIds($tag_ids)->relation('Things')
which will return a ManyManyList that you can iterate over, ie
foreach(Tag::get()->byIds($tag_ids)->relation('Things') as $thing){
Debug::Dump($thing); // A 'Thing' object
}
Hope this helps
I have a script which successfully creates new nodes. But I'm having trouble setting the taxonomy before saving.
I believe in Drupal 6 I would use this method.
$cat1_tid = taxonomy_get_term_by_name($data[$i]['cat1']);
$cat2_tid = taxonomy_get_term_by_name($data[$i]['cat2']);
$cat3_tid = taxonomy_get_term_by_name($data[$i]['cat3']);
$node->taxonomy = array($cat1_tid, $cat2_tid, $cat3_tid);
I think in Drupal 7 I would do this (my field name is Catalog)
$node->taxonomy_catalog['und'][0] = array($term1Obj, $term2Obj);
taxonomy_get_term_by_name doesn't seem to return the correct object to insert into the node object.
If anyone can shed some light, appreciated.
Thanks
EDIT
Solution:
// Taxonomy
$categories = array($data[$i]['cat1'], $data[$i]['cat2'], $data[$i]['cat3']);
foreach ($categories as $key => $category) {
if ($term = taxonomy_get_term_by_name($category)) {
$terms_array = array_keys($term);
$node->taxonomy_catalog[LANGUAGE_NONE][$key]['tid'] = $terms_array['0'];
}
}
Below is some quick-and-dirty code I used recently to import "command" nodes into a site. Mid-way down, the foreach loop takes care of creating and assigning terms, as needed.
$command = new stdClass;
$command->language = LANGUAGE_NONE;
$command->uid = 1;
$command->type = 'drubnub';
$command->title = $line['0'];
$command->body[LANGUAGE_NONE]['0']['value'] = $line['1'];
$command->url[LANGUAGE_NONE]['0']['value'] = trim($line['2']);
$command->uses[LANGUAGE_NONE]['0']['value'] = $line['3'];
$tags = explode(',', $line['4']);
foreach ($tags as $key => $tag) {
if ($term = taxonomy_get_term_by_name($tag)) {
$terms_array = array_keys($term);
$command->field_tags[LANGUAGE_NONE][$key]['tid'] = $terms_array['0'];
} else {
$term = new STDClass();
$term->name = $tag;
$term->vid = 1;
if (!empty($term->name)) {
$test = taxonomy_term_save($term);
$term = taxonomy_get_term_by_name($tag);
foreach($term as $term_id){
$command->product_tags[LANGUAGE_NONE][$key]['tid'] = $term_id->tid;
}
$command->field_tags[LANGUAGE_NONE][$key]['tid'] = $tid;
}
}
}
node_save($command);
Here you are, this code successfully add a new term to the node before the node is created.
$my_term_name = 'micky';
$term_array = taxonomy_get_term_by_name($my_term_name);
if($term_array == array()){
//empty term ..
$term->name = $my_term_name;
$term->vid = 1;
taxonomy_term_save($term);
$term_array = taxonomy_get_term_by_name($my_term_name);
}
//get the first index of the array .
foreach ($term_array as $tid => $term_object)break;
$node->field_tag['und'][$tid] = (array)$term_object;
Perhaps my experience is unique, but I found that using
$term = taxonomy_get_term_by_name($tag)
$tid = $term->tid;
caused an error.
I found that after $term is saved, there is no need to fetch the newly created term.
The $term object is updated to include the new tid.
Any answer that use LANGUAGE_NONE or 'und' to alter a field is not the proper way of doing it as it assumes that the drupal site is one language. The proper way to edit a field is to use entity_metadata_wrapper.
$node_wrapper = entity_metadata_wrapper('node', $node);
// If you have Entity API [entity] module installed you can simply.
$node_wrapper = $node->wrapper();
// It is good practice to check the terms in the field before adding
// a new one to make sure that the term is not already set.
$term_ids_current = $node_wrapper->taxonomy_catalog->raw();
if (!in_array($term_new_id, $term_ids_current)) {
$node_wrapper->taxonomy_catalog[] = $term_new_id;
}
// To add multiple terms iterate an array or terms ids.
$term_ids_current = $node_wrapper->taxonomy_catalog->raw();
$tern_new_ids = array(1, 2, 3);
foreach ($term_new_ids as $term_new_id) {
if (!in_array($term_new_id, $term_ids_current)) {
$node_wrapper->taxonomy_catalog[] = $term_new_id;
}
}
// To remove a term.
$term_ids_current = $node_wrapper->taxonomy_catalog->raw();
$delta = array_search($term_remove_id, $term_ids_current);
if (is_int($delta)) {
$node_wrapper->taxonomy_catalog->offsetUnset($delta);
}
// To replace all terms.
$term_new_ids = array(1, 2, 3);
$node_wrapper->taxonomy_catalog->set($term_new_ids);
// To get all the fully loaded terms in a field.
$terms = $node_wrapper->taxonomy_catalog->value();
// At the end make sure to save it.
$node_wrapper->save();
I was trying to do do a boolean search 'sname:'.$user->name.' OR sname:xxxxxx; , i am not getting any results where as sname:xxxxxx; works fine. i even added mm=1 with the query modify hook. can somebody guide me how i can accomplish this.
Here is my code.....
$keys = "";
$filters = 'sname:'.$user->name.' OR sname:xxxxxx;
//print($filters);
$solrsort = variable_get('apachesolr_search_taxonomy_sort', 'created desc');
$page = isset($_GET['page']) ? $_GET['page'] : 0;
$_GET['retain-filters'] = 1;
try {
//stolen from search.module (search_data)
$data = apachesolr_search_execute($keys, $filters, $solrsort, 'search/apachesolr_search', $page);
$results = theme('search_results', $data, $type);
} catch (Exception $e){
watchdog('apachesolr', t('Error running search'));
}
function reports_apachesolr_modify_query(&$query, &$params, $caller) {
// I only want to see articles by the admin!
$params['mm'] = 1;
}
Adv thanks.
The $user object is not available in some places, so it depends where you are calling it from. You need to invoke it globally. Add the following to the top of your code.
$keys = "";
global $user; //Add this line
$filters = 'sname:'.$user->name.' OR sname:xxxxxx';
Also note that you are missing a closing quotation mark after the xxxxxx. I added it in the above code.
Did you try removing the second sname:?? My SOLR filters look like:
(fname:carrie OR carol)
AND
(lname:miller OR jones)