Completed
Push — 1.9 ( 482137...30c3e5 )
by
unknown
43:10
created

getCustomerListQueryBuilder()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 45
rs 8.8571
cc 3
eloc 31
nc 3
nop 1
1
<?php
2
namespace OroCRM\Bundle\ChannelBundle\Entity\Manager;
3
4
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
5
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\Common\Persistence\ObjectManager;
8
use Doctrine\ORM\EntityManager;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
11
use Oro\Bundle\EntityBundle\ORM\QueryUtils;
12
use Oro\Bundle\EntityBundle\ORM\SqlQueryBuilder;
13
use Oro\Bundle\SearchBundle\Engine\Indexer as SearchIndexer;
14
use Oro\Bundle\SearchBundle\Query\Result as SearchResult;
15
use Oro\Bundle\SearchBundle\Query\Result\Item as SearchResultItem;
16
use Oro\Bundle\SearchBundle\Event\PrepareResultItemEvent;
17
use Oro\Bundle\SoapBundle\Entity\Manager\ApiEntityManager;
18
19
class CustomerSearchApiEntityManager extends ApiEntityManager
20
{
21
    const DEFAULT_CHANNEL_FIELD_NAME = 'dataChannel';
22
23
    const CHANNEL_ENTITY_CLASS = 'OroCRM\Bundle\ChannelBundle\Entity\Channel';
24
25
    const CUSTOMER_IDENTITY_INTERFACE = 'OroCRM\Bundle\ChannelBundle\Model\CustomerIdentityInterface';
26
27
    /** @var SearchIndexer */
28
    protected $searchIndexer;
29
30
    /** @var EventDispatcherInterface */
31
    protected $dispatcher;
32
33
    /**
34
     * {@inheritdoc}
35
     * @param SearchIndexer            $searchIndexer
36
     * @param EventDispatcherInterface $dispatcher
37
     */
38
    public function __construct(
39
        $class,
40
        ObjectManager $om,
41
        SearchIndexer $searchIndexer,
42
        EventDispatcherInterface $dispatcher
43
    ) {
44
        parent::__construct($class, $om);
45
        $this->searchIndexer = $searchIndexer;
46
        $this->dispatcher   = $dispatcher;
47
    }
48
49
    /**
50
     * Gets search result
51
     *
52
     * @param int           $page   Page number
53
     * @param int           $limit  Number of items per page
54
     * @param string        $search The search string.
55
     * @param Criteria|null $criteria
56
     *
57
     * @return array
58
     */
59
    public function getSearchResult($page = 1, $limit = 10, $search = '', $criteria = null)
60
    {
61
        $searchQuery = $this->searchIndexer->getSimpleSearchQuery(
62
            $search,
63
            $this->getOffset($page, $limit),
64
            $limit,
65
            $this->getCustomerSearchAliases()
0 ignored issues
show
Documentation introduced by
$this->getCustomerSearchAliases() is of type array<integer,string>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
66
        );
67
68
        if ($criteria && $expression = $criteria->getWhereExpression()) {
69
            $searchQuery->getCriteria()->andWhere($expression);
70
        }
71
72
        $searchResult = $this->searchIndexer->query($searchQuery);
73
74
        $result = [
75
            'result'     => [],
76
            'totalCount' =>
77
                function () use ($searchResult) {
78
                    return $searchResult->getRecordsCount();
79
                }
80
        ];
81
82
        if ($searchResult->count() > 0) {
83
            $customers = $this->getCustomerListQueryBuilder($searchResult)->getQuery()->getResult();
84
85
            $result['result'] = $this->mergeResults($searchResult, $customers);
86
        }
87
88
        return $result;
89
    }
90
91
    /**
92
     * Merges the search result and customers
93
     *
94
     * @param SearchResult $searchResult
95
     * @param array        $customers
96
     *
97
     * @return array
98
     */
99
    protected function mergeResults(SearchResult $searchResult, array $customers)
100
    {
101
        $result = [];
102
103
        /** @var SearchResultItem $item */
104
        foreach ($searchResult as $item) {
105
            $this->dispatcher->dispatch(PrepareResultItemEvent::EVENT_NAME, new PrepareResultItemEvent($item));
106
107
            $id        = $item->getRecordId();
108
            $className = $item->getEntityName();
109
110
            $resultItem = [
111
                'id'      => $id,
112
                'entity'  => $className,
113
                'title'   => $item->getRecordTitle(),
114
                'channel' => null
115
            ];
116
117
            foreach ($customers as $customer) {
118
                if ($customer['entity'] === $className && $customer['id'] === $id) {
119
                    $resultItem['channel'] = $customer['channelId'];
120
                    break;
121
                }
122
            }
123
124
            $result[] = $resultItem;
125
        }
126
127
        return $result;
128
    }
129
130
    /**
131
     * Returns a query builder that could be used for fetching the list of the Customer entities
132
     * filtered by ids.
133
     *
134
     * @param SearchResult $searchResult
135
     *
136
     * @return SqlQueryBuilder
137
     */
138
    protected function getCustomerListQueryBuilder(SearchResult $searchResult)
139
    {
140
        /** @var EntityManager $em */
141
        $em = $this->getObjectManager();
142
143
        $selectStmt = null;
144
        $subQueries = [];
145
        foreach ($this->getCustomerListFilters($searchResult) as $customerClass => $customerIds) {
146
            $subQb = $em->getRepository($customerClass)->createQueryBuilder('e')
147
                ->select(
148
                    sprintf(
149
                        'channel.id AS channelId, e.id AS entityId, \'%s\' AS entityClass',
150
                        str_replace('\'', '\'\'', $customerClass)
151
                    )
152
                )
153
                ->innerJoin('e.' . $this->getChannelFieldName($customerClass), 'channel');
154
            $subQb->where($subQb->expr()->in('e.id', $customerIds));
155
156
            $subQuery = $subQb->getQuery();
157
158
            $subQueries[] = QueryUtils::getExecutableSql($subQuery);
159
160
            if (empty($selectStmt)) {
161
                $mapping    = QueryUtils::parseQuery($subQuery)->getResultSetMapping();
162
                $selectStmt = sprintf(
163
                    'entity.%s AS channelId, entity.%s as entityId, entity.%s AS entityClass',
164
                    QueryUtils::getColumnNameByAlias($mapping, 'channelId'),
165
                    QueryUtils::getColumnNameByAlias($mapping, 'entityId'),
166
                    QueryUtils::getColumnNameByAlias($mapping, 'entityClass')
167
                );
168
            }
169
        }
170
171
        $rsm = QueryUtils::createResultSetMapping($em->getConnection()->getDatabasePlatform());
172
        $rsm
173
            ->addScalarResult('channelId', 'channelId', 'integer')
174
            ->addScalarResult('entityId', 'id', 'integer')
175
            ->addScalarResult('entityClass', 'entity');
176
        $qb = new SqlQueryBuilder($em, $rsm);
0 ignored issues
show
Deprecated Code introduced by
The class Oro\Bundle\EntityBundle\ORM\SqlQueryBuilder has been deprecated with message: since 1.9. Use {@see Oro\Component\DoctrineUtils\ORM\SqlQueryBuilder} instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
177
        $qb
178
            ->select($selectStmt)
179
            ->from('(' . implode(' UNION ALL ', $subQueries) . ')', 'entity');
180
181
        return $qb;
182
    }
183
184
    /**
185
     * Extracts ids of the Customer entities from a given search result
186
     *
187
     * @param SearchResult $searchResult
188
     *
189
     * @return array example: ['Acme\Entity\Customer' => [1, 2, 3], ...]
190
     */
191
    protected function getCustomerListFilters(SearchResult $searchResult)
192
    {
193
        $filters = [];
194
        /** @var SearchResultItem $item */
195
        foreach ($searchResult as $item) {
196
            $entityClass = $item->getEntityName();
197
            if (!isset($filters[$entityClass])) {
198
                $filters[$entityClass] = [];
199
            }
200
            $filters[$entityClass][] = $item->getRecordId();
201
        }
202
203
        return $filters;
204
    }
205
206
    /**
207
     * Gets the field name for many-to-one relation between the Customer the Channel entities
208
     *
209
     * @param string $customerClass The FQCN of the Customer entity
210
     *
211
     * @return string
212
     *
213
     * @throws \RuntimeException if the relation not found
214
     */
215
    protected function getChannelFieldName($customerClass)
216
    {
217
        /** @var ClassMetadata $metadata */
218
        $metadata = $this->getObjectManager()->getClassMetadata($customerClass);
219
        if ($metadata->hasAssociation(self::DEFAULT_CHANNEL_FIELD_NAME)
220
            && $metadata->getAssociationTargetClass(self::DEFAULT_CHANNEL_FIELD_NAME) === self::CHANNEL_ENTITY_CLASS
221
        ) {
222
            return self::DEFAULT_CHANNEL_FIELD_NAME;
223
        }
224
225
        $channelAssociations = $metadata->getAssociationsByTargetClass(self::CHANNEL_ENTITY_CLASS);
226
        foreach ($channelAssociations as $fieldName => $mapping) {
227
            if ($mapping['type'] === ClassMetadata::MANY_TO_ONE && $mapping['isOwningSide']) {
228
                return $fieldName;
229
            }
230
        }
231
232
        throw new \RuntimeException(
233
            sprintf(
234
                'The entity "%s" must have many-to-one relation to "%s".',
235
                $customerClass,
236
                self::CHANNEL_ENTITY_CLASS
237
            )
238
        );
239
    }
240
241
    /**
242
     * Gets all class names for all the Customer entities
243
     *
244
     * @return string[]
245
     */
246
    protected function getCustomerEntities()
247
    {
248
        return array_map(
249
            function (ClassMetadata $metadata) {
250
                return $metadata->name;
251
            },
252
            array_filter(
253
                $this->getObjectManager()->getMetadataFactory()->getAllMetadata(),
254
                function (ClassMetadata $metadata) {
255
                    // @todo: should be removed in CRM-3263
256
                    if ($metadata->name === 'OroCRM\Bundle\ChannelBundle\Entity\CustomerIdentity') {
257
                        return false;
258
                    }
259
260
                    return
261
                        !$metadata->isMappedSuperclass
262
                        && $metadata->getReflectionClass()->isSubclassOf(self::CUSTOMER_IDENTITY_INTERFACE);
263
                }
264
            )
265
        );
266
    }
267
268
    /**
269
     * Returns search aliases for all the Customer entities
270
     *
271
     * @return string[]
272
     */
273
    protected function getCustomerSearchAliases()
274
    {
275
        return array_values(
276
            $this->searchIndexer->getEntityAliases($this->getCustomerEntities())
277
        );
278
    }
279
}
280