DQL on ManyToMany relationship : multiple where parameters - doctrine-orm

I have 2 entities with a relation ManyToMany
class User{
/**
* Owning Side
*
* #ORM\ManyToMany(targetEntity="Aire\AppBundle\Entity\Project", inversedBy="projectFollowed")
* #ORM\JoinTable(name="project_followed",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="project_id", referencedColumnName="id")}
* )
**/
private $projectFollowed;
}
class Project{
/**
* Inverse Side
*
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="projectFollowed")
**/
private $projectFollowed;
}
I need to check in my project_followed table if there is already a user assigned to this project.
$dql = '
SELECT user
FROM Application\Sonata\UserBundle\Entity\User user
INNER JOIN user.projectFollowed project_followed
WHERE project_followed.project_id = :project_id
AND project_followed.user_id = :user_id
';
$em = $this->getDoctrine()->getManager();
$query = $em
->createQuery( $dql )
->setParameter( 'project_id', $project_id )
->setParameter( 'user_id', $user_id )
;
It seems my DQL is not correct :
Class AppBundle\Entity\Project has no field or association named project_id
So if i change
WHERE project_followed.project_id = :project_id
to
WHERE project_followed.id = :project_id
It is working but how can i make the relation with the user id?
The reason of this is because i need to check if there is no duplicate entries.

Related

Doctrine: Automatic ResultMapping of Native Query?

After some research, I made this UNION query work in my Repository class:
class PostRepository extends ServiceEntityRepository {
// ...
public function getLatestPostsOfUser ($limit = 10) : ?array {
$sql = <<<SQL
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN cms_user_follow ON (p.parent_type = cms_user_follow.followed_entity_type AND p.parent_id = cms_user_follow.followed_entity_id)
WHERE cms_user_follow.user_id = {$this->currentUser->getId()}
UNION
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN project_memberships ON (p.parent_type = 'Project' AND p.parent_id = project_memberships.project_id)
WHERE project_memberships.user_id = {$this->currentUser->getId()} and project_memberships.status = 1
ORDER BY created DESC
LIMIT $limit
SQL;
$res = [];
try {
$rsm = (new ResultSetMapping())
->addEntityResult(Post::class, 'p')
->addFieldResult('p', 'id', 'id')
->addFieldResult('p', 'title', 'title')
->addFieldResult('p', 'content', 'content')
->addFieldResult('p', 'images', 'images')
->addFieldResult('p', 'parent_id', 'parentId')
->addFieldResult('p', 'parent_type', 'parentType')
->addFieldResult('p', 'created', 'created')
->addFieldResult('p', 'last_modified', 'lastModified')
;
$res = $this->getEntityManager()->createNativeQuery($sql, $rsm)->getArrayResult();
} catch (DBALException $e) {
}
return $res;
}
}
It involves an awefull lot of manual field mapping, so I was wondering wheather there is an automatic solution to this?
Many thx!
It looks like Doctrine can do something like this under the hood and also apply the mappings automatically:
$qb = $this->em->createQueryBuilder();
$where = $qb->expr()->andX(
$qb->expr()->eq('f.followedEntityId', ':parentId'),
$qb->expr()->eq('f.followedEntityType', ':parentType'),
$qb->expr()->eq('f.following', 1),
$qb->expr()->eq('u.allowEmailNotifications', 1),
$qb->expr()->eq('u.enabled', 1),
);
$qb->select('f', 'u')
->from(UserFollow::class, 'f')
->leftJoin(
User::class,
'u',
Join::WITH,
'f.userId = u.id'
)
->where($where)
->setParameters([
'parentId' => $post->getParentId(),
'parentType' => $post->getParentType()
])
;
$usersAndFollowings = $qb->getQuery()->getResult();
$usersAndFollowings is then a flat array with both entities alternating: [UserFollow, User, UserFollow, User, ...]
You'll probably want to process it afterwards, so that connected UserFollow and User entities are together in a sub array.

ProgrammingError at / ORDER BY "id" is ambiguous LINE 1: ...logapp_article.userid=blogapp_useradd.uname ORDER BY id DESC

Exception Value:
ORDER BY "id" is ambiguous
LINE 1: ...logapp_article.userid=blogapp_useradd.uname ORDER BY id DESC
Error location from views.py
def Index(request):
ad1 = ads.objects.raw("select * from blogapp_ads order by id desc limit 1")
ad2 = ads.objects.raw("select * from blogapp_ads order by id desc limit 1 offset 1")
ad34 = ads.objects.raw("select * from blogapp_ads order by id desc limit 2 offset 2")
ob1 = news.objects.raw("select * from blogapp_news order by id desc limit 5")
obb = article.objects.raw(
"select * from blogapp_article inner join blogapp_useradd on blogapp_article.userid=blogapp_useradd.uname ORDER BY id DESC LIMIT 14")
obj = article.objects.raw(
"select * from blogapp_article inner join blogapp_useradd on blogapp_article.userid=blogapp_useradd.uname ORDER BY id DESC LIMIT 5")
ob = article.objects.raw(
"select * from blogapp_article inner join blogapp_useradd on blogapp_article.userid=blogapp_useradd.uname ORDER BY id DESC")
return render(request, 'Guest/Index.html', context={'data9':obb, 'data3': ob1,'data1': ob, 'data2':obj, 'time': now,'data4':ad1,'data5':ad2,'data6':ad34})
Without visibility into the tables, I'm assuming "id" exists as an attribute in both of the tables you are joining. Try changing your join statement to:
ob = article.objects.raw(
"select * from blogapp_article as a inner join blogapp_useradd as b on a.userid=b.uname ORDER BY a.id DESC")

Unable to save new "ActiveRecord" in database through Codeception

I have a simple Unit test in which I'm trying to create a new record in "orders" table . So when the test is run it throws an exception :
[yii\db\IntegrityException] SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'created_by' cannot be null
I guess this is due to the so called "BlamemableBehavior" trying to update the "created_by" column . So I tried to detach it and to manually pass the "created_by" value . Neither of both worked . Can you please help ?
<?php
namespace frontend\tests\unit\models;
// use common\fixtures\UserFixture;
use frontend\components\Payments;
use common\models\order\Order;
class RandomTest extends \Codeception\Test\Unit
{
/**
* #var \frontend\tests\UnitTester
*/
protected $tester;
protected $order;
public function _before(){
//this doesn't work
$this->order = $this->tester->haveRecord(Order::class,
[
'id' => 577,
'payment_type' => 4,
'status' => 1,
'amount' => 1,
'created_by' => 561,
'updated_by' => 561,
]);
}
public function testRandom(){
//this does not work either
/*$model = new Order;
$model->detachBehavior('BlameableBehavior');
$model->payment_type = 4;
$model->status = 1;
$model->amount = 1;
$model->created_by = 561;
$model->updated_by = 561;
$model->save();*/
}
}
The "Order" model :
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[] = [
'class' => \common\components\behaviors\modelLog\ActiveRecordHistoryBehavior::className(),
'manager' => '\common\components\behaviors\modelLog\managers\DBManager',
'ignoreFields' => ['updated_at', 'created_at']
];
return $behaviors;
}
/**
* #inheritdoc
*/
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['paid'] = [
'status', 'invoice_number', 'paid_date', 'is_used', 'allocation'];
return $scenarios;
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['status', 'payment_type', 'invoice_reference', 'invoice_number'], 'integer'],
[['payment_type', 'amount', 'vat', 'total_amount', 'credit', 'invoice_reference'], 'required'],
[['amount', 'vat', 'total_amount', 'credit', 'discount'], 'number'],
[['reason'], 'trim'],
[['allocation'], 'string']
];
}

Anyone else having trouble getting custom handlers to wrok on D8 entities?

I'm starting out with Drupal 8 and until now I have been quite impressed with all the new functionality. However, I have been trying to write my own entity and I'm running into trouble:
This is the entity definition:
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace Drupal\entitytest\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Defines the Candidate Entity
*
* #ingroup entitytest
*
* #ContentEntityType(
* id="entitytest_AuditionCandidate",
* label=#Translation("Candidate"),
* base_table="candidate",
*
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\entitytest\Entity\Controller\CandidateListBuilder",
* "form" = {
* "add" = "Drupal\Core\Entity\ContentEntityForm",
* "edit" = "Drupal\Core\Entity\ContentEntityForm",
* "delete" = "Drupal\EntityTest\Form\CandidateDeleteForm",
* },
* },
* admin_permission="administer candidates",
* entity_keys={
* "id"="id",
* "label"="lastname",
* },
* links = {
* "canonical" = "/AuditionCandidate/view/{entitytest_AuditionCandidate}",
* "edit-form" = "/AuditionCandidate/edit/{entitytest_AuditionCandidate}",
* },
* )
*/
class Candidate extends ContentEntityBase {
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('ID'))
->setDescription(t('The ID of the Contact entity.'))
->setReadOnly(TRUE);
$fields['lastname'] = BaseFieldDefinition::create('string')
->setLabel(t('Last Name'))
->setDescription(t('The name of the Contact entity.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -6,
))
->setDisplayOptions('form', array(
'type' => 'string',
'weight' => -6,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['firstname'] = BaseFieldDefinition::create('string')
->setLabel(t('First Name'))
->setDescription(t('The name of the Contact entity.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -6,
))
->setDisplayOptions('form', array(
'type' => 'string',
'weight' => -6,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
}
So I am trying to edit the Deleteform from this entity. I have created a file under /modules/custom/EntityTest/src/Form/CandidateFormDelete.php
The code in this file is as follows:
<?php
namespace Drupal\EntityTest\Form;
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
class CandidateDeleteForm extends ContentEntityConfirmFormBase {
public function getQuestion() {
return $this->t('Are you sure?');
}
public function getCancelUrl() {
return new Url('entity.entitytest_AuditionCandidate.collection');
}
public function getConfirmText() {
return $this->t('Delete');
}
}
I have also added a route for the delete form:
entity.entitytest_AuditionCandidate.delete_form:
path: 'AuditionCandidate/delete/{entitytest_AuditionCandidate}'
defaults:
_entity_form: entitytest_AuditionCandidate.delete
_title: 'Delete Candidate'
requirements:
_permission: 'administer candidates'
But when I try to open /AuditionCandidate/delete/1 I'm getting the following error message:
Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException: The "entitytest_AuditionCandidate" entity type did not specify a "delete" form class. in Drupal\Core\Entity\EntityManager->getFormObject() (line 309 of core/lib/Drupal/Core/Entity/EntityManager.php).
It just doesn't seem to make sense since I have definined a class for the deleteform.
Anyone who can see what I am missing? It's possibly just a typo but I have been staring at it for quite some time now and I just can't figure it out.
Matt.
Drupal 8 implements the PSR-4 standard for package-based PHP namespace autoloading.
In that case the name of your class file doesn't correspond to the name of the actual class used.
The file name should also be "CandidateDeleteForm.php" instead of "CandidateFormDelete"
That's the reason why you are getting that exception.
For more about that subject read:
https://www.drupal.org/node/2156625

Doctrine2 - Many-to-many Association with association type

Is it possible to create an external field in Association in Doctrine2. The main purpose is to have a type of association.
For example,
We have Contacts and Opportunities. I need the association between Contacts and Opportunities with a type of this association.
Example of data:
contact_id | opportunity_id | association_type
------------------------------------------------------
<contact_id> | <opportunity_id> | <Executive Sponsor>
<contact_id> | <opportunity_id> | <Business Evaluator>
Is it possible to implement in Doctrine2?
Here is my association (YAML):
Opportunity:
type: entity
table: opportinity
...
...
...
manyToMany:
contacts:
targetEntity: Contact
joinTable:
name: opportinities_contacts
joinColumns:
opportunity_id:
referencedColumnName: id
inverseJoinColumns:
contact_id:
referencedColumnName: id
Thanks
The best practice in this case is to create an Entity Association Class.
Basically, split your Many-To-Many relationship into a pair of Many-to-one with a new class inbetween
Create a new class "ContactOpportunities" (In my organization we name them ToMap => ContactToOpportunityMap that sits between the classes.
class ContactOpportunity {
/**
* #var <FQN>\Contact
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="<FQN>\Contact", inversedBy='opportunities')
* #ORM\JoinColumns({
* #ORM\JoinColumn(name='Contact_ID', referencedColumnName="id")
* })
protected $contact;
/**
* #var <FQN>\Opportunity
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="<FQN>\Opportunity", inversedBy='contacts')
* #ORM\JoinColumns({
* #ORM\JoinColumn(name='Opportunity_ID', referencedColumnName="id")
* })
protected $opportunity;
/*
* #var string type
*
* #ORM\Column(name="Association_Type", type="string")
protected $type;
}
Or in yml...
ContactOpportunity
type: entity
table: opportunities_contacts
...
...
...
manyToOne:
targetEntity: Contact
inversedBy: opportunities
joinColumn:
name: contact_id
referencedColumnName: id
manyToOne:
targetEntity: Opportunity
inversedBy: contacts
joinColumn:
name: opportunity_id
referencedColumnName: id
Then convert your existing classes to target this new class:
Opportunity:
type: entity
table: opportunity
...
...
...
oneToMany:
contacts:
targetEntity: ContactOpportunity
mappedBy: opportunity
Contact:
type: entity
table: contact
...
...
...
oneToMany:
opportunities:
targetEntity: ContactOpportunity
mappedBy: contact