I am working on a dynamic search module in yii2 and todays task is to create the filter for lists. For example Dropdown lists where you can select multiple conditions. In our case it is a state of a car. So you can select if a car is Active or Inactive or Sold. But there is a scenario where the user wants to search both.
The filter works well if you search only the state of the car, it generates the following SQL:
SELECT * FROM `gepkocsi` WHERE (((`allapot`='A')) OR ((`allapot`='F'))) AND (`torolt` IS NULL) LIMIT 20
It works because there aren't any further condition like licence plate. Now lets se what happens if we add that condition into our search.
So at this time we search for cars where the licence plat contain the i letter and the status is Active or Saled. The following SQL is generated:
SELECT * FROM `gepkocsi` WHERE (((`rendszam` LIKE '%i%') OR ((`allapot`='A'))) OR ((`allapot`='E'))) AND (`torolt` IS NULL) LIMIT 20
The problem is that after the licence plate (rendszam) shuold be a AND insted of OR. This is generated by the following method. And this leads us to the main source of the problem.
/**
* #param $fieldName
*/
public function filterList($fieldName) {
if (isset($fieldName) && !empty($this->$fieldName)) {
foreach ($this->$fieldName as $singleFieldName) {
$this->query->orFilterWhere(['or',
[$fieldName => $singleFieldName]]);
}
}
}
I think if I add separatly all the fieldnames as a parameter of orFilterWhere it should work. What do you think what is the best practice to do it? Or Yii2 has someting magical to do that?
Thank you for your answers!
I found the solution how to append all the OR conditions. Simply the Yii2 generates an IN caluse.
/**
* #param $fieldName
*/
public function filterList($fieldName) {
if (isset($fieldName) && !empty($this->$fieldName)) {
//$this->debug($this->$fieldName);
$tomb = [];
foreach ($this->$fieldName as $singleFieldName) {
$tomb[$fieldName][] = $singleFieldName;
}
$this->query->andFilterWhere($tomb);
}
}
Related
So I am quite new to Laravel, and I have a situation, where I am trying to gather data from a pivot table (contains 2 foreign keys only) in order to retrieve data from other tables.
Before everything, I'd like to note, that word "campaign" is the same as "box". Simply it differs in database and front.
I have multiple boxes, that contains specific gifts.
I have set the URL of the box to be something as such: http://127.0.0.1:8000/box/1
http://127.0.0.1:8000/box/2
etc..
I have done so, by simply using a button with the {id}:
View the box
My plan is, to print out only that specific boxes gifts (right now, all boxes print out all gifts).
I have tried to use the ->where option within my function, although, it seems that I can't try equaling to the campaigns ID.
Incorrect code:
function box(){
$data = array(
'list'=>DB::table('campaigns_gifts')
->join('gift_items', 'gift_items.id', '=', 'campaigns_gifts.gift_foreignK')
->select('gift_items.*')
->where($campaign_foreignK = '{id}')
->get()
);
return view('DBqueries.boxView', $data);
}
My question is, how can I specifically return data, that only belongs to that specific box, since I am not able to use mysql where option.
For reference, these are the database tables:
Basically, I would need to match my URL's id with campaign_foreignK
Thank you in advance.
First of all, yout need to start to use Laravel Eloquent Models.
But doing by your way (the hardest):
You need to create a route in web or api, something like that:
Route::get('/box/{id}', [BoxController::class, 'view']);
Then you need to put this function on your controller:
function view($id){
/**
* You can do it by 2 ways:
* 1 - Do a where in the result of DB query (the bad way)
*/
$list = DB::table('campaigns_gifts')
->join('gift_items', 'gift_items.id', '=', 'campaigns_gifts.gift_foreignK')
->select('gift_items.*')
->where($campaign_foreignK = '{id}')
->get();
$list = (array)collect($list)->where('abc', 123);
/**
* Or the second way (the best is to use the Eloquent, but using DB the following is the best)
* 1 - Get the relations:
* Is git_items id the key for gift_foreignK ? i'm supposing that is it! so....
*/
$giftsIds = array_values((array)DB::select("select * from campaigns_gifts where campaign_foreignK = $id"));
$giftsIdsString = implode($giftsIds, ',');
$list = (array)DB::select("select * from gift_items where id in ($giftsIdsString)");
return view('DBqueries.boxView', ['list' => $list]);
}
I'm struggling with the TYPO3 l10n and the modifying of localized records.
Short Question:
How can I get the localized record from my extbase model?
In more detail:
I am using a backend module to modify multiple records at the same time. At the moment it only works for origin records. But the customer wants to use this module to edit localized records also.
This is what I tryed so far:
An array is passing the origin uid's to the repository class. Depending on the SysLanguageUid I am doing a findByUid if its an origin record and if the SysLanguageUid is anything higher than 0 I do the following query:
protected function findByUidAndSysLanguageUid($uid, $sysLanguageUid) {
$query = $this->createQuery();
$query->matching(
$query->equals('l10n_parent', $uid),
$query->equals('sys_language_uid', $sysLanguageUid)
);
return $query->execute();
}
This query works fine for the first record. But what really confuses me is, ongoing from the second entry the query returns the origin records (even while the sys_language_uid in the query is set to >0).
Any ideas how to handle this?
PS: If you need some more information then let me know it.
UPDATE:
So far I managed it to get the raw query from the above constraint:
Query of the first record:
SELECT tx_extkey_domain_model_mymodel.*
FROM tx_extkey_domain_model_mymodel
WHERE (tx_extkey_domain_model_mymodel.l10n_parent = '133' AND tx_extkey_domain_model_mymodel.sys_language_uid = '1') AND
(tx_extkey_domain_model_mymodel.sys_language_uid IN (1, -1) OR
(tx_extkey_domain_model_mymodel.sys_language_uid = 0 AND
tx_extkey_domain_model_mymodel.uid NOT IN (SELECT tx_extkey_domain_model_mymodel.l10n_parent
FROM tx_extkey_domain_model_mymodel
WHERE tx_extkey_domain_model_mymodel.l10n_parent > 0 AND
tx_extkey_domain_model_mymodel.sys_language_uid = 1 AND
tx_extkey_domain_model_mymodel.deleted = 0))) AND
tx_extkey_domain_model_mymodel.hidden = 0 AND (tx_extkey_domain_model_mymodel.starttime 1479390060) AND
tx_extkey_domain_model_mymodel.deleted = 0
ORDER BY tx_extkey_domain_model_mymodel.name ASC
LIMIT 1;
Query of the second record:
SELECT tx_extkey_domain_model_mymodel.*
FROM tx_extkey_domain_model_mymodel
WHERE (tx_extkey_domain_model_mymodel.l10n_parent = '134' AND tx_extkey_domain_model_mymodel.sys_language_uid = '1') AND
(tx_extkey_domain_model_mymodel.sys_language_uid IN (1, -1) OR
(tx_extkey_domain_model_mymodel.sys_language_uid = 0 AND
tx_extkey_domain_model_mymodel.uid NOT IN (SELECT tx_extkey_domain_model_mymodel.l10n_parent
FROM tx_extkey_domain_model_mymodel
WHERE tx_extkey_domain_model_mymodel.l10n_parent > 0 AND
tx_extkey_domain_model_mymodel.sys_language_uid = 1 AND
tx_extkey_domain_model_mymodel.deleted = 0))) AND
tx_extkey_domain_model_mymodel.hidden = 0 AND (tx_extkey_domain_model_mymodel.starttime 1479390360) AND
tx_extkey_domain_model_mymodel.deleted = 0
ORDER BY tx_extkey_domain_model_mymodel.name ASC
LIMIT 1;
UPDATE 2
This now confuses me even more...
I put both of the sql queries into heidisql and run them manually. They work perfectly!
So it seems like there is no problem with the query itself.
UPDATE 3
This is the method of the repository which gets called by the controller.
/**
* #param array $parentUidCollection
* #param int $L
*/
protected function updateByCollection(array $parentUidCollection, $L = 0) {
//$L is the language $_GET parameter. cant use TSFE because of inside of a backend module
if($L > 0) {
$this->setTempQuerySettings($L);
}
foreach ($parentUidCollection as $parentUid){
$myModel = $this->findTranslatedByParentId($parentUid)->getFirst();
$myModel->setDescription('foo');
$this->update($myModel);
}
}
My defaultQuerySettings are overwritten in the third line if the actual language is not the default language.
/**
* #param $sysLanguageUid
*/
protected function setTempQuerySettings($sysLanguageUid) {
/** #var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $tempQuerySettings */
$this->originalQuerySettings = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
$tempQuerySettings = clone $this->originalQuerySettings;
$tempQuerySettings->setRespectStoragePage(false);
$tempQuerySettings->setRespectSysLanguage(true);
$tempQuerySettings->setLanguageUid($sysLanguageUid);
$tempQuerySettings->setLanguageMode(false);
$tempQuerySettings->setLanguageOverlayMode(false);
$this->setDefaultQuerySettings($tempQuerySettings);
}
And now with the function suggessted by Toke Herkild but without the query settings inside. they are set in the above snipped.
/**
* #param int|string $parentUid
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findTranslatedByParentId($parentUid)
{
$query = $this->createQuery();
$query->matching($query->equals('l10n_parent', $parentUid));
return $query->execute();
}
UPDATE 4:
After executing the code the database looks like this:
The 100 uid's are the origin and the 200 are the localized records in this picture.
NOTICE: Below solution would work except for this bug:
https://forge.typo3.org/issues/47192
Maybe just make it simple, inside your ModelRepository do something like:
public function findTranslatedByParentId($parentUid) {
$query = $this->createQuery()
$qrySettings = $query->getQuerySettings();
$qrySettings->setLanguageMode('ignore');
$qrySettings->setLanguageOverlay(FALSE);
$query->setDefaultQuerySettings($qrySettings);
return $query->matching($query->equals('l18n_parent', $parentUid))->execute();
}
You need to disable the persistence layers language handling or it believes you try to fetch the localized version of the record for your current sys_language.
I am building an intranet application and i want to be able to have 2 different types of users a regular user and an admin user. I am trying to figure out what would be the best way to go about doing this. Either to have one object for admin type stuff and then one object for user type stuff. Or combine both of that into one object. But i keep getting stuck and not sure how to go about doing that, or if that is even the best way.
Lets say I have the following situations:
1. query the db to get all tasks for all projects that are active.
Admin Query
2. query the db to get all tasks for all projects that are due today and active.
Admin Query
3. Query the db to get all tasks for a specific project that are active.
Admin Query
User Query
4. Query the db to get all tasks for a specific project that are active and due today.
Admin Query
User Query
5. Query the db to get all tasks for a specific project.
Admin Query
User Query
6. Query the db to get all tasks for a specific project, with different status specified.
Admin Query
7. Any one of those queries has an optional parameter to either get the count or the data.
I started the following object but now im a little stuck as which route to go:
public function getTasks($status, $project, $type = "count", $duetoday = NULL)
{
try
{
if($duetoday != NULL){
$today = date("Y-m-d");
$stmt = $this->db->prepare("SELECT * FROM tasks WHERE status=:status
AND $project=:project AND duedate BETWEEN :duedate
AND :duedate");
$stmt->execute(array(':status'=>$status,':project'=>$project,':duedate'=>$today));
}else{
$stmt = $this->db->prepare("SELECT * FROM tasks WHERE status=:status
AND $project=:project");
$stmt->execute(array(':status'=>$status,':project'=>$project));
}
$tasks=$stmt->fetch(PDO::FETCH_ASSOC);
if($stmt->rowCount() > 0)
{
if($type == "count"){
return $stmt->rowCount();
}else{
return $tasks;
}
}else{
return false;
}
}
catch(PDOException $e)
{
echo $e->getMessage();
}
}
I will start with some words about the single responsibility principle. Basically, this means that an object and it's behaviors should have one responsibility. Here, I think your getTasks method is a good opportunity to refactor some code into better object oriented code.
There are actually many things it is doing:
Generate sql
Execute a query
Control the flow of the program
The method generating sql should not have to worry about it's execution, and the method executing it should not have to worry about getting it. This, as a side effect, will also reduce the nesting in a single method.
There is a lot of code to write, which I'll let you do, but if you create classes that implements those interfaces and a controller to use them, you should be able to get through this and write easier to maintain / refactor code:
interface SqlGenerating {
/**
* #param array $params
* #return string
*/
public function makeSql(array $params);
/**
* #param array $params
* #return array
*/
public function makeValues(array $params);
}
interface DBAccessing {
public function __construct(\PDO $pdo);
/**
* #param string $sql
* #param array $values
* #return PDOStatement
*/
public function getStmt($sql, array $values = []);
}
class Controller {
public function __construct(SqlGenerating $sqlGenerator, DBAccessing $dbAccess) {
// associate to private properties
}
public function getTasks($status, $project, $type = "count", $duetoday = null) {
// this function will use the sqlGenerator and the dbAccess to query the db
// this function knows to return the count or the actual rows
}
}
If you haven't already, this is a good time to learn about type-hinting in functions. This requires your function to be passed an object (or an array) to be assured of the behavior of the function. Also, you will notice that I type-hinted the interfaces into the controller. This is to actually be able to switch classes if ever you need a different one to manage sql and db access.
Whenever a user searches, I get this error:
2012-06-26 11:05:21.671 [NOTICE] [208.69.120.120:48175-0#hostname] [STDERR] PHP Fatal error: Call to undefined method Mage_Catalog_Model_Resource_Product_Flat::getEntityTablePrefix() in /chroot/home/SITENAME/DOMAIN.COM/html/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php on line 505
And instead of the user's results appearing, they get a blank white page- no error on their end, no UI, just white. This was the first issue I noticed, but on the same day the following issues started coming up:
White Search Results
Sub-category product count for all sub-categories in layered nav is showing 0.
Some customers can not view orders from their front-end UI when logged in.
Our order export script is returning blank fields (1.7mb instead of 4.3).
Our "Made-in-the-usa" and "best sellers" pages are returning more products than they should.
Now, I know these are all incorrect because if I reindex the entire site, for some period while it is processing the index, all of the above works. However, when the index is complete, it all breaks again. The same day this happened we had an error page appear that stated one of the tables had broken and should be repaired. We ran PHPMyAdmin's repair and optimize functions on all tables and it fixed that error- but all of these are still broken.
Any ideas at all? Any ideas of what could be tried to fix this? I cant find this error anywhere- and the guys over at Nexcess haven't been able to find anything for this, either.
Thank you for your time.
As per the comments above, Magento's telling you that it's trying to call the method getEntityTablePrefix on an object whose classes don't have that method defined. Specifically in this method
#File: app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php
public function getBackendTable()
{
if ($this->_dataTable === null) {
if ($this->isStatic()) {
$this->_dataTable = $this->getEntityType()->getValueTablePrefix();
} else {
$backendTable = trim($this->_getData('backend_table'));
if (empty($backendTable)) {
$entityTable = array($this->getEntity()->getEntityTablePrefix(), $this->getBackendType());
$backendTable = $this->getResource()->getTable($entityTable);
}
$this->_dataTable = $backendTable;
}
}
return $this->_dataTable;
}
Given this happens from the following class
Mage_Catalog_Model_Resource_Product_Flat
It says to me that you have an extension and/or customization done that assumes you're not using the flat catalog data tables and wasn't coded to work with the flat table.
Dropping in a debugging call like this
if(!is_callable(array($this->getEntity()),'getEntityTablePrefix'))
{
mageDebugBacktrace();
//debug_print_backtrace();
exit;
}
right before the offending call (in a local code pool override, of course), will print out a call-stack that should point the offending code.
The seems that problem is in Mage_CatalogSearch_Model_Resource_Search_Collection::_getSearchEntityIdsSql that is not compatible with using the product flat index.
You can rewrite class Mage_CatalogSearch_Model_Resource_Search_Collection and do two little modifications.
1) Add new function _getSearchEntityIdsSqlUsingFlatIndex to rewrited class. This new function (I hope) does exactly the same thing as original _getSearchEntityIdsSql, but with using the product flat index.
2) Modify function _getSearchEntityIdsSql so that it calls new _getSearchEntityIdsSqlUsingFlatIndex if the catalog product flat index is enabled and built.
See source code:
class VENDOR_MODULE_Model_PATHTOREWRITECLASS extends Mage_CatalogSearch_Model_Resource_Search_Collection {
/**
* Retrieve SQL for search entities using product flat index.
*
* #param $query
* #return Varien_Db_Select
*/
protected function _getSearchEntityIdsSqlUsingFlatIndex($query)
{
/* #var $coreHelper Mage_Core_Model_Resource_Helper_Abstract */
$coreHelper = Mage::getResourceHelper('core');
$likeOptions = array('position' => 'any');
$flatTableName = $this->getTable('catalog/product_flat').'_'.$this->getStoreId();
/** #var Varien_Db_Select $select */
$select = $this->getConnection()
->select()
->from($flatTableName, array('entity_id'));
foreach ($this->_getAttributesCollection() as $attribute) {
/** #var Mage_Catalog_Model_Entity_Attribute $attribute */
if ($this->_isAttributeTextAndSearchable($attribute)) {
$attributeCode = $attribute->getAttributeCode();
$dbFieldName = in_array($attribute->getFrontendInput(), array('select', 'multiselect'))
? $attributeCode.'_value'
: $attributeCode;
if ($this->getConnection()->tableColumnExists($flatTableName, $dbFieldName)) {
$select->where($coreHelper->getCILike($dbFieldName, $this->_searchQuery, $likeOptions));
} else {
Mage::log(__METHOD__.": Attribute '$attributeCode' is missing in flat index.", Zend_Log::NOTICE);
}
}
}
return $select;
}
/**
* Retrieve SQL for search entities
*
* #param unknown_type $query
* #return string
*/
protected function _getSearchEntityIdsSql($query)
{
// HACK - make compatibility with flat index
/** #var Mage_Catalog_Helper_Product_Flat $flatHelper */
$flatHelper = Mage::helper('catalog/product_flat');
if ($this->getStoreId() > 0
&& $flatHelper->isEnabled($this->getStoreId())
&& $flatHelper->isBuilt($this->getStoreId())
) {
return $this->_getSearchEntityIdsSqlUsingFlatIndex($query);
}
// END HACK
return parent::_getSearchEntityIdsSql($query);
}
}
When using the ACL implementation in Symfony2 in a web application, we have come across a use case where the suggested way of using the ACLs (checking a users permissions on a single domain object) becomes unfeasible. Thus, we wonder if there exists some part of the ACL API we can use to solve our problem.
The use case is in a controller that prepares a list of domain objects to be presented in a template, so that the user can choose which of her objects she wants to edit. The user does not have permission to edit all of the objects in the database, so the list must be filtered accordingly.
This could (among other solutions) be done according to two strategies:
1) A query filter that appends a given query with the valid object ids from the present user's ACL for the object(or objects). I.e:
WHERE <other conditions> AND u.id IN(<list of legal object ids here>)
2) A post-query filter that removes the objects the user does not have the correct permissions for after the complete list has been retrieved from the database. I.e:
$objs = <query for objects>
$objIds = <getting all the permitted obj ids from the ACL>
for ($obj in $objs) {
if (in_array($obj.id, $objIds) { $result[] = $obj; }
}
return $result;
The first strategy is preferable as the database is doing all the filtering work, and both require two database queries. One for the ACLs and one for the actual query, but that is probably unavoidable.
Is there any implementation of one of these strategies (or something achieving the desired results) in Symfony2?
Assuming that you have a collection of domain objects that you want to check, you can use the security.acl.provider service's findAcls() method to batch load in advance of the isGranted() calls.
Conditions:
Database was populated with test entities, with object permissions of MaskBuilder::MASK_OWNER for a random user from my database, and class permissions of MASK_VIEW for role IS_AUTHENTICATED_ANONYMOUSLY; MASK_CREATE for ROLE_USER; and MASK_EDIT and MASK_DELETE for ROLE_ADMIN.
Test Code:
$repo = $this->getDoctrine()->getRepository('Foo\Bundle\Entity\Bar');
$securityContext = $this->get('security.context');
$aclProvider = $this->get('security.acl.provider');
$barCollection = $repo->findAll();
$oids = array();
foreach ($barCollection as $bar) {
$oid = ObjectIdentity::fromDomainObject($bar);
$oids[] = $oid;
}
$aclProvider->findAcls($oids); // preload Acls from database
foreach ($barCollection as $bar) {
if ($securityContext->isGranted('EDIT', $bar)) {
// permitted
} else {
// denied
}
}
RESULTS:
With the call to $aclProvider->findAcls($oids);, the profiler shows that my request contained 3 database queries (as anonymous user).
Without the call to findAcls(), the same request contained 51 queries.
Note that the findAcls() method loads in batches of 30 (with 2 queries per batch), so your number of queries will go up with larger datasets. This test was done in about 15 minutes at the end of the work day; when I have a chance, I'll go through and review the relevant methods more thoroughly to see if there are any other helpful uses of the ACL system and report back here.
Itinerating over the entities is not feasible if you have a couple of thousandth entities - it will keep getting slower and consuming more memory, forcing you to use doctrine batching capabilities, thus making your code more complex (and innefective because after all you need only the ids to make a query - not the whole acl/entities in memory)
What we did to solve this problem is to replace acl.provider service with our own and in that service add a method to make a direct query to the database:
private function _getEntitiesIdsMatchingRoleMaskSql($className, array $roles, $requiredMask)
{
$rolesSql = array();
foreach($roles as $role) {
$rolesSql[] = 's.identifier = ' . $this->connection->quote($role);
}
$rolesSql = '(' . implode(' OR ', $rolesSql) . ')';
$sql = <<<SELECTCLAUSE
SELECT
oid.object_identifier
FROM
{$this->options['entry_table_name']} e
JOIN
{$this->options['oid_table_name']} oid ON (
oid.class_id = e.class_id
)
JOIN {$this->options['sid_table_name']} s ON (
s.id = e.security_identity_id
)
JOIN {$this->options['class_table_nambe']} class ON (
class.id = e.class_id
)
WHERE
{$this->connection->getDatabasePlatform()->getIsNotNullExpression('e.object_identity_id')} AND
(e.mask & %d) AND
$rolesSql AND
class.class_type = %s
GROUP BY
oid.object_identifier
SELECTCLAUSE;
return sprintf(
$sql,
$requiredMask,
$this->connection->quote($role),
$this->connection->quote($className)
);
}
Then calling this method from the actual public method that gets the entities ids:
/**
* Get the entities Ids for the className that match the given role & mask
*
* #param string $className
* #param string $roles
* #param integer $mask
* #param bool $asString - Return a comma-delimited string with the ids instead of an array
*
* #return bool|array|string - True if its allowed to all entities, false if its not
* allowed, array or string depending on $asString parameter.
*/
public function getAllowedEntitiesIds($className, array $roles, $mask, $asString = true)
{
// Check for class-level global permission (its a very similar query to the one
// posted above
// If there is a class-level grant permission, then do not query object-level
if ($this->_maskMatchesRoleForClass($className, $roles, $requiredMask)) {
return true;
}
// Query the database for ACE's matching the mask for the given roles
$sql = $this->_getEntitiesIdsMatchingRoleMaskSql($className, $roles, $mask);
$ids = $this->connection->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);
// No ACEs found
if (!count($ids)) {
return false;
}
if ($asString) {
return implode(',', $ids);
}
return $ids;
}
This way now we can use the code to add filters to DQL queries:
// Some action in a controller or form handler...
// This service is our own aclProvider version with the methods mentioned above
$aclProvider = $this->get('security.acl.provider');
$ids = $aclProvider->getAllowedEntitiesIds('SomeEntityClass', array('role1'), MaskBuilder::VIEW, true);
if (is_string($ids)) {
$queryBuilder->andWhere("entity.id IN ($ids)");
}
// No ACL found: deny all
elseif ($ids===false) {
$queryBuilder->andWhere("entity.id = 0")
}
elseif ($ids===true) {
// Global-class permission: allow all
}
// Run query...etc
Drawbacks: This methods have to be improved to take into account the complexities of ACL inheritance and strategies, but for simple use cases it works fine. Also a cache has to be implemented to avoid the repetitive double query (one with class-level, another with objetc-level)
Coupling Symfony ACL back to application and using it as sorting, is not good approach. You are mixing and coupling 2 or 3 layers of application together.
ACL functionality is to answer "YES/NO" to question "Am I allowed to do this?" If you need some sort of owned/editable articles, you can use some column like CreatedBy or group CreatedBy by criteria from another table. Some usergroups or accounts.
Use joins, and in case you're using Doctrine, get it to generate joins for you, as they are almost always faster. Therefore you should design your ACL schema that doing these fast filters are feasible.