Doctrine query WHERE IN - many to many - php

I am building out a hotel website in Symfony2. Each hotel can provide many board basis options such as Self Catering, All-Inclusive etc.
On my search form the users can filter by all of the usual fields such as location, price, star rating and board basis. Board basis is a multiple select check box.
When a user selects multiple board basis options, I am currently handling it in this way... (which is throwing errors)
$repo = $this->getDoctrine()->getRepository("AppBundle:Accommodation");
$data = $form->getData();
$qb = $repo->createQueryBuilder("a")
->innerJoin("AppBundle:BoardType", "b")
->where("a.destination = :destination")
->setParameter("destination", $data['destination'])
->andWhere("a.status = 'publish'");
if (count($data['boardBasis']) > 0) {
$ids = array_map(function($boardBasis) {
return $boardBasis->getId();
}, $data['boardBasis']->toArray());
$qb->andWhere($qb->expr()->in("a.boardBasis", ":ids"))
->setParameter("ids", $ids);
}
Here is the property declaration on a hotel entity
/**
* #ORM\ManyToMany(targetEntity="BoardType")
* #ORM\JoinTable(name="accommodation_board_type",
* joinColumns={#ORM\JoinColumn(name="accommodation_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="board_type_id", referencedColumnName="id")}
* )
*/
private $boardBasis;
The error I am currently getting is:
[Semantical Error] line 0, col 177 near 'boardBasis I': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
On submitting the form and using var_dump on the board types I am getting:
object(Doctrine\Common\Collections\ArrayCollection)[3043]
private 'elements' =>
array (size=2)
0 =>
object(AppBundle\Entity\BoardType)[1860]
protected 'shortCode' => string 'AI' (length=2)
protected 'id' => int 1
protected 'name' => string 'All-Inclusive' (length=13)
protected 'description' => null
protected 'slug' => string 'all-inclusive' (length=13)
protected 'created' =>
object(DateTime)[1858]
...
protected 'updated' =>
object(DateTime)[1863]
...
1 =>
object(AppBundle\Entity\BoardType)[1869]
protected 'shortCode' => string 'BB' (length=2)
protected 'id' => int 2
protected 'name' => string 'Bed & Breakfast' (length=15)
protected 'description' => null
protected 'slug' => string 'bed-breakfast' (length=13)
protected 'created' =>
object(DateTime)[1867]
...
protected 'updated' =>
object(DateTime)[1868]
...
I cannot seem to find the correct syntax for this query, I have done a few times in the past (and it was a pain each time), but I just cannot remember how it is done. I have tried without mapping the ID's, passing the ArrayCollection directly in.
The only thing I can think of at the moment is to switch it to using createQuery and using DQL and see if that makes any difference.
Any help with this issue would be appreciated, Thank you

It looks to me like you're join is not fully complete. You were missing a statement describing what field to join on:
$qb = $repo->createQueryBuilder("a")
->innerJoin("AppBundle:BoardType", "b")
->where("a.boardBasis = b.id")
...
Or you could join like that:
$qb = $repo->createQueryBuilder("a")
->innerJoin("a.boardBasis", "b")
...
Then you can do add your WHERE IN statement like so:
$qb->andWhere('b.id IN (:ids)')
->setParameter('ids', $ids);

Related

Get behavior related configuration from code

Are behavior related configurations accessible? In this specific case, a behavior was attached to a table. I would like to know if it's possible to get the fields property in some way, later on in code?
<?php
class MyRandomTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
...
// Add Cipher behavior
$this->addBehavior('CipherBehavior.Cipher', [
'fields' => [
'original' => 'string',
'changed' => 'string',
]
]);
}
...
}
?>
If I load the table and dump the content, I do not see the behavior listed:
$table = TableRegistry::get('MyRandomTable');
var_dump($table);
Partial dump content:
protected '_behaviors' =>
object(Cake\ORM\BehaviorRegistry)[170]
protected '_table' =>
&object(Cake\ORM\Table)[172]
protected '_methodMap' =>
array (size=0)
empty
protected '_finderMap' =>
array (size=0)
empty
protected '_loaded' =>
array (size=0)
empty
protected '_eventManager' =>
object(Cake\Event\EventManager)[165]
protected '_listeners' =>
array (size=0)
...
protected '_isGlobal' => boolean false
protected '_eventList' => null
protected '_trackEvents' => boolean false
protected '_eventClass' => string '\Cake\Event\Event' (length=17)
What I'd like to do, in a controller, is to get the fields and pass those on to a view.
Edit #1
Using CakePHP v3.3.16
Edit #2
I'm seeing behavior information as I had missed the plugin prefix when loading the table:
$table = TableRegistry::get('PluginName.MyRandomTable');
Shows:
protected '_behaviors' =>
object(Cake\ORM\BehaviorRegistry)[143]
protected '_table' =>
&object(PluginName\Model\Table\MyRandomTable)[94]
protected '_methodMap' =>
array (size=4)
'timestamp' =>
array (size=2)
...
'touch' =>
array (size=2)
...
'encrypt' =>
array (size=2)
...
'decrypt' =>
array (size=2)
...
protected '_finderMap' =>
array (size=0)
empty
protected '_loaded' =>
array (size=2)
'Timestamp' =>
object(Cake\ORM\Behavior\TimestampBehavior)[181]
...
'Cipher' =>
object(CipherBehavior\Model\Behavior\CipherBehavior)[192]
...
protected '_eventManager' =>
object(Cake\Event\EventManager)[175]
protected '_listeners' =>
array (size=4)
...
protected '_isGlobal' => boolean false
protected '_eventList' => null
protected '_trackEvents' => boolean false
protected '_eventClass' => string '\Cake\Event\Event' (length=17)
First of all your table class file is incorrect, it needs a namespace, otherwise it cannot be found, and you'll end up with an instance of \Cake\ORM\Table (a so called auto/generic-table) instead of concrete subclass thereof, hence your behavior is missing.
That being said, it depends on how the behavior has been programmed. If it follows the default configuration pattern, then you can access the configuration via its config() or getConfig() (as of CakePHP 3.4) methods.
Of course you have to access the behavior, not just the table class to which it is attached. This is done using the behavior registry, which is available via the Table::behaviors() method:
$fields = $table->behaviors()->get('Cipher')->config('fields');
See also
Cookbook > Database Access & ORM > Behaviors > Accessing Loaded Behaviors
Cookbook > Database Access & ORM > Behaviors > Re-configuring Loaded Behaviors
API > \Cake\ORM\BehaviorRegistry
API > \Cake\ORM\Behavior
Cookbook > Configuration > Disabling Generic Tables
You can get Column names of a table by schema()->columns().
Example -
$getColumnArray = $this->Users->schema()->columns();//return Users Table Colums Name Array
$getColumnArray = $this->Users->associations()->keys()//return Users assocation table key

How to extends properly Cms Page and Block WebAPI

I would like to extends/override the APIs of CMS Pages and Blocks to add custom fields (for example, an array of store_ids to allow the creation of a CMS block on different stores with an webapi call, it's my main goal).
I tried many things, but this does not seem clean, standards-compliant. I have the impression of overloading the whole module CMS (data interface, repository interface, model, repository factory, etc..) just for adding new fields in webapi.
I will explain what I have done for now :
1/ I wrote an new data api \BlockInterface who extends the core interface to add my new field (store_id) and declare his getter and setter methods.
namespace Reflet\CmsExtension\Api\Data;
interface BlockInterface extends \Magento\Cms\Api\Data\BlockInterface
{
/**
* New fields
*/
const STORE_ID = 'store_id';
/**
* Get Store Ids
*
* #return array
*/
public function getStoreId();
/**
* Set Store Ids
*
* #param array $storeIds
* #return \Reflet\CmsExtension\Api\Data\BlockInterface
*/
public function setStoreId($storeIds);
}
2/ I implements the Data Interface in the new \Block model object.
3/ With many in the di.xml file, I override the block model type.
4/ I reimplement the api \BlockRepositoryInterface and the webapi.xml file to change the data type used in the repository.
5/ I testing the getById() and the save() methods for the block #1 with simple repository call and webapi.
Here, is my result :
1/ If, I get the block #1 with the repository, the store_id is load in the result object.
array (size=12)
'row_id' => string '1' (length=1)
'block_id' => string '1' (length=1)
'created_in' => string '1' (length=1)
'updated_in' => string '2147483647' (length=10)
'title' => string 'Catalog Events Lister' (length=21)
'identifier' => string 'catalog_events_lister' (length=21)
'content' => string '{{block class="Magento\\CatalogEvent\\Block\\Event\\Lister" name="catalog.event.lister" template="lister.phtml"}}' (length=113)
'creation_time' => string '2017-02-14 14:33:20' (length=19)
'update_time' => string '2017-02-14 14:33:20' (length=19)
'is_active' => string '1' (length=1)
'store_id' =>
array (size=1)
0 => string '0' (length=1)
'stores' =>
array (size=1)
0 => string '0' (length=1)
2/ If, I get the block #1 with the WebAPI, the store_id is also load in the result object.
public 'store_id' =>
array (size=1)
0 => string '0' (length=1)
public 'id' => int 1
public 'identifier' => string 'catalog_events_lister' (length=21)
public 'title' => string 'Catalog Events Lister' (length=21)
public 'content' => string '{{block class="Magento\\CatalogEvent\\Block\\Event\\Lister" name="catalog.event.lister" template="lister.phtml"}}' (length=113)
public 'creation_time' => string '2017-02-14 14:33:20' (length=19)
public 'update_time' => string '2017-02-14 14:33:20' (length=19)
public 'active' => boolean true
3/ If, I save the block #1 with the WebAPI, an error occurs, he load the wrong BlockInterface. I need to reimplement the BlockRepository ? I don't think it's the good and cleaner method (...) ?
public 'message' => string 'Property "StoreId" does not have corresponding setter in class "Magento\Cms\Api\Data\BlockInterface".' (length=101)
public 'trace' => string '#0 /var/www/vhosts/reflet.com/storemanager/vendor/magento/framework/Reflection/NameFinder.php(59): Magento\Framework\Reflection\NameFinder->findAccessorMethodName(Object(Zend\Code\Reflection\ClassReflection), 'StoreId', 'getStoreId', 'isStoreId')
#1 /var/www/vhosts/reflet.com/storemanager/vendor/magento/framework/Webapi/ServiceInputProcessor.php(158): Magento\Framework\Reflection\NameFinder->getGetterMethodName(Object(Zend\Code\Reflection\ClassReflection), 'StoreId')
#2 /var/www/vhosts/reflet.com/storemanag'... (length=2239)
For this example, I use the 'store_id' only, but in the future, it's must be works with all custom fields in Page or Block object.
How do you recommend this modification to be as clean as possible and limit conflicts in the future ? Do you have an example ?
If you want to test, you can find in this archive, my two current Magento modules (Reflet_Api an simple adapter to call REST WebApi and Reflet_CmsExtension to override CMS Blocks) : https://drive.google.com/file/d/0B12trf96j0ALSjhZdmJPTzBPT0U/view
Thank you for your answers.
Best regards.
Jonathan

doctrine2 inner join data in object

So I am doing two inner joins:
<?php
$q->select('o, pic.path pic1, pic_.path pic2');
$q->innerJoin('\TestBundle\Entity\UserImages', 'pic', 'WITH', 'o.id = pic.user');
$q->where('pic.keyname = \'pic1\'');
$q->innerJoin('\TestBundle\Entity\UserImages', 'pic_', 'WITH', 'o.id = pic.user');
$q->where('pic_.keyname = \'pic2\'');
This works but the returned result is this:
array (size=1)
0 =>
array (size=3)
0 =>
object(TestBundle\Entity\user)[1359]
private 'name' => string 'test' (length=12)
private 'created' =>
object(DateTime)[1347]
...
private 'modified' => null
private 'deleted' => null
private 'id' => int 1
'pic1' => string 'pic1.png' (length=8)
'pic2' => string 'pic2.png' (length=8)
Is there any way to have the pic1 and pic2 be set in the object?
I tried adding setPic1/Pic2 to the Users entity but that didn't work.
What do I have to do to get this to work in the result of the query?
Thanks
I didn't find a way to do what I wanted using just doctrine but since I am using sonota and this is the reason I needed the pic1 and pic2 I created a DataGrid Decorator and intercepted the getResult() method.
In the admin class I override the buildDataGrid (don't have the source code now but I think that was the method) and it will create the datagrid but then I will do something like:
$this->datagrid = new DataGridDecorator($this->datagrid)
The decorator will then change the result given by the method getResult() to what sonata expects and so I don't have to override I don't know how many templates.
Thanks for the help!

Amazon Advertising API get Node's products

I'm creating e-commerce shop with Amazon API integration.
The problem I faced with is I cannot get items from specific node.
So I've tried many ways to do that, last one was something like this:
$fields = array();
$fields['AssociateTag'] = "ItemSearch";
$fields['Condition'] = 'All';
$fields['Operation'] = 'ItemSearch';
$fields['Version'] = '2013-08-01';
$fields['BrowseNode'] = $catId;
$fields['ResponseGroup'] = "Images,ItemAttributes,Offers";
$fields['Service'] = 'AWSECommerceService';
$fields['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
$fields['AWSAccessKeyId'] = $this->accessKey;
After that call I have the output:
public 'Items' =>
object(SimpleXMLElement)[150]
public 'Request' =>
object(SimpleXMLElement)[139]
public 'IsValid' => string 'True' (length=4)
public 'ItemSearchRequest' =>
object(SimpleXMLElement)[138]
public 'BrowseNode' => string '1289481011' (length=10)
public 'Condition' => string 'All' (length=3)
public 'ResponseGroup' =>
array (size=3)
0 => string 'Images' (length=6)
1 => string 'ItemAttributes' (length=14)
2 => string 'Offers' (length=6)
So I can see my request, but no items were returned to me.
By the way, ItemLookup, ItemSearch with keywords and BrowseNode operations work just fine.
What can I do to get items from node with spicific ID without using keywords?
I figured out what happened. I didn't set SearchIndex when I point BrowseNode ID.
So there was no result.

CakePHP- saving associated model data

I have two models .. One is called Schedule and the other ScheduleContact.
Schedule belongsTo ScheduleContact
From an action belonging to scheduleController, I want to save values coming from a serialized form for the main model as well as the associated model.
In my view I have the following
<?php echo $this->Form->input('Schedule.start_at', array('id' => 'start_at', 'type' => 'hidden')); ?>
<?php echo $this->Form->input('Schedule.finish_at', array('id' => 'finish_at', 'type' => 'hidden')); ?>
<?php echo $this->Form->input('ScheduleContact.0.name', array('id' => 'name','div' => 'inline-input')); ?>
<?php echo $this->Form->input('ScheduleContact.0.surname', array('id' => 'surname','div' => 'inline-input compact')); ?>
etc..
The request data comes in this form (from var_dump($this->request->data)):
array (size=3)
'Schedule' =>
array (size=3)
'start_at' => string '2014-3-25 11:15' (length=15)
'finish_at' => string '2014-03-25 11:30:00' (length=19)
'donation_method_id' => string '1' (length=1)
'ScheduleContact' =>
array (size=1)
0 =>
array (size=6)
'name' => string 'Jane' (length=4)
'surname' => string 'Powell' (length=6)
'address' => string 'Hamilton Hodell, 5th Floor 66-68 Margaret Street' length=39)
'city' => string 'London' (length=6)
'tel_no' => string '+44(0)20-7636 1221' (length=18)
'email' => string 'powell_jane#gmail.com' (length=22)
'log' =>
array (size=3)
'ds' => string 'default' (length=7)
'sql' => string 'SELECT `Schedule`.`id`, `Schedule`.`donation_method_id`, `Schedule`.`schedule_contact_id`, `Schedule`.`start_at`, `Schedule`.`finish_at`, `DonationMethod`.`id`, `DonationMethod`.`donation_method`, `DonationMethod`.`recovery_time`, `DonationMethod`.`duration`, `ScheduleContact`.`id`, `ScheduleContact`.`name`, `ScheduleContact`.`surname`, `ScheduleContact`.`address`, `ScheduleContact`.`city`, `ScheduleContact`.`tel_no`, `ScheduleContact`.`email`, `ScheduleContact`.`donor_id` FROM `blood_services_db`.`schedule'... (length=852)
'hash' => string '5f2b2b3a462f6555cb5290fb49c42df04a7948e0' (length=40)
Finally I am trying to save the data like so :
if($this->Schedule->saveAssociated($this->request->data)){
which is not saving anything to the database, therefore the this if condition is never met.
What could be wrong ? Thanks
Basically the problem was that saveAssociated needs to be applied in the main model. With that in mind, as it was set up above, the main model had a bolongsTo relationship, which means that the current model contains the foreign key., which is also indeed correct. So where is the problem you might ask?
The problem is that saveAsocciated is equivalent to
$user = $this->User->save($this->request->data);
// If the user was saved, Now we add this information to the data
// and save the Profile.
if (!empty($user)) {
// The ID of the newly created user has been set
// as $this->User->id.
$this->request->data['Profile']['user_id'] = $this->User->id;
}
Which means that it attempts to save the main model first. Therefore my setup was incorrect since the main model needed to be ScheduleContact, since the foreign key will be saved inside the Schedule model after data in ScheduleContact id successfully inserted.
There you go... CakePHP is Great once you get past that learning curve.
Your Model-Name is breaking Cake's naming convention. It should be:
ScheduleContact
A few questions to give you a better answer:
Can you add the Model Association Variables ($belongsTo, etc)?
Do you use Containable behaviour?

Categories