Completed
Push — 1.9 ( 5c3e2e...1529fd )
by
unknown
60:20
created

getCustomerEntities()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 21
rs 9.3142
cc 3
eloc 12
nc 1
nop 0
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\Query\ResultSetMapping;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
12
use Oro\Bundle\EntityBundle\ORM\QueryUtils;
13
use Oro\Bundle\EntityBundle\ORM\SqlQueryBuilder;
14
use Oro\Bundle\SearchBundle\Engine\Indexer as SearchIndexer;
15
use Oro\Bundle\SearchBundle\Query\Result as SearchResult;
16
use Oro\Bundle\SearchBundle\Query\Result\Item as SearchResultItem;
17
use Oro\Bundle\SearchBundle\Event\PrepareResultItemEvent;
18
use Oro\Bundle\SoapBundle\Entity\Manager\ApiEntityManager;
19
20
class CustomerSearchApiEntityManager extends ApiEntityManager
21
{
22
    const DEFAULT_CHANNEL_FIELD_NAME = 'dataChannel';
23
24
    const CHANNEL_ENTITY_CLASS = 'OroCRM\Bundle\ChannelBundle\Entity\Channel';
25
26
    const CUSTOMER_IDENTITY_INTERFACE = 'OroCRM\Bundle\ChannelBundle\Model\CustomerIdentityInterface';
27
28
    /** @var SearchIndexer */
29
    protected $searchIndexer;
30
31
    /** @var EventDispatcherInterface */
32
    protected $dispatcher;
33
34
    /**
35
     * {@inheritdoc}
36
     * @param SearchIndexer            $searchIndexer
37
     * @param EventDispatcherInterface $dispatcher
38
     */
39
    public function __construct(
40
        $class,
41
        ObjectManager $om,
42
        SearchIndexer $searchIndexer,
43
        EventDispatcherInterface $dispatcher
44
    ) {
45
        parent::__construct($class, $om);
46
        $this->searchIndexer = $searchIndexer;
47
        $this->dispatcher   = $dispatcher;
48
    }
49
50
    /**
51
     * Gets search result
52
     *
53
     * @param int           $page   Page number
54
     * @param int           $limit  Number of items per page
55
     * @param string        $search The search string.
56
     * @param Criteria|null $criteria
57
     *
58
     * @return array
59
     */
60
    public function getSearchResult($page = 1, $limit = 10, $search = '', $criteria = null)
61
    {
62
        $searchQuery = $this->searchIndexer->getSimpleSearchQuery(
63
            $search,
64
            $this->getOffset($page, $limit),
65
            $limit,
66
            $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...
67
        );
68
69
        if ($criteria && $expression = $criteria->getWhereExpression()) {
70
            $searchQuery->getCriteria()->andWhere($expression);
71
        }
72
73
        $searchResult = $this->searchIndexer->query($searchQuery);
74
75
        $result = [
76
            'result'     => [],
77
            'totalCount' =>
78
                function () use ($searchResult) {
79
                    return $searchResult->getRecordsCount();
80
                }
81
        ];
82
83
        if ($searchResult->count() > 0) {
84
            $customers = $this->getCustomerListQueryBuilder($searchResult)->getQuery()->getResult();
85
86
            $result['result'] = $this->mergeResults($searchResult, $customers);
87
        }
88
89
        return $result;
90
    }
91
92
    /**
93
     * Merges the search result and customers
94
     *
95
     * @param SearchResult $searchResult
96
     * @param array        $customers
97
     *
98
     * @return array
99
     */
100
    protected function mergeResults(SearchResult $searchResult, array $customers)
101
    {
102
        $result = [];
103
104
        /** @var SearchResultItem $item */
105
        foreach ($searchResult as $item) {
106
            $this->dispatcher->dispatch(PrepareResultItemEvent::EVENT_NAME, new PrepareResultItemEvent($item));
107
108
            $id        = $item->getRecordId();
109
            $className = $item->getEntityName();
110
111
            $resultItem = [
112
                'id'      => $id,
113
                'entity'  => $className,
114
                'title'   => $item->getRecordTitle(),
115
                'channel' => null
116
            ];
117
118
            foreach ($customers as $customer) {
119
                if ($customer['entity'] === $className && $customer['id'] === $id) {
120
                    $resultItem['channel'] = $customer['channelId'];
121
                    break;
122
                }
123
            }
124
125
            $result[] = $resultItem;
126
        }
127
128
        return $result;
129
    }
130
131
    /**
132
     * Returns a query builder that could be used for fetching the list of the Customer entities
133
     * filtered by ids.
134
     *
135
     * @param SearchResult $searchResult
136
     *
137
     * @return SqlQueryBuilder
138
     */
139
    protected function getCustomerListQueryBuilder(SearchResult $searchResult)
140
    {
141
        /** @var EntityManager $em */
142
        $em = $this->getObjectManager();
143
144
        $selectStmt = null;
145
        $subQueries = [];
146
        foreach ($this->getCustomerListFilters($searchResult) as $customerClass => $customerIds) {
147
            $subQb = $em->getRepository($customerClass)->createQueryBuilder('e')
148
                ->select(
149
                    sprintf(
150
                        'channel.id AS channelId, e.id AS entityId, \'%s\' AS entityClass',
151
                        str_replace('\'', '\'\'', $customerClass)
152
                    )
153
                )
154
                ->innerJoin('e.' . $this->getChannelFieldName($customerClass), 'channel');
155
            $subQb->where($subQb->expr()->in('e.id', $customerIds));
156
157
            $subQuery = $subQb->getQuery();
158
159
            $subQueries[] = QueryUtils::getExecutableSql($subQuery);
160
161
            if (empty($selectStmt)) {
162
                $mapping    = QueryUtils::parseQuery($subQuery)->getResultSetMapping();
163
                $selectStmt = sprintf(
164
                    'entity.%s AS channelId, entity.%s as entityId, entity.%s AS entityClass',
165
                    QueryUtils::getColumnNameByAlias($mapping, 'channelId'),
166
                    QueryUtils::getColumnNameByAlias($mapping, 'entityId'),
167
                    QueryUtils::getColumnNameByAlias($mapping, 'entityClass')
168
                );
169
            }
170
        }
171
172
        $rsm = new ResultSetMapping();
173
        $rsm
174
            ->addScalarResult('channelId', 'channelId', 'integer')
175
            ->addScalarResult('entityId', 'id', 'integer')
176
            ->addScalarResult('entityClass', 'entity');
177
        $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...
178
        $qb
179
            ->select($selectStmt)
180
            ->from('(' . implode(' UNION ALL ', $subQueries) . ')', 'entity');
181
182
        return $qb;
183
    }
184
185
    /**
186
     * Extracts ids of the Customer entities from a given search result
187
     *
188
     * @param SearchResult $searchResult
189
     *
190
     * @return array example: ['Acme\Entity\Customer' => [1, 2, 3], ...]
191
     */
192
    protected function getCustomerListFilters(SearchResult $searchResult)
193
    {
194
        $filters = [];
195
        /** @var SearchResultItem $item */
196
        foreach ($searchResult as $item) {
197
            $entityClass = $item->getEntityName();
198
            if (!isset($filters[$entityClass])) {
199
                $filters[$entityClass] = [];
200
            }
201
            $filters[$entityClass][] = $item->getRecordId();
202
        }
203
204
        return $filters;
205
    }
206
207
    /**
208
     * Gets the field name for many-to-one relation between the Customer the Channel entities
209
     *
210
     * @param string $customerClass The FQCN of the Customer entity
211
     *
212
     * @return string
213
     *
214
     * @throws \RuntimeException if the relation not found
215
     */
216
    protected function getChannelFieldName($customerClass)
217
    {
218
        /** @var ClassMetadata $metadata */
219
        $metadata = $this->getObjectManager()->getClassMetadata($customerClass);
220
        if ($metadata->hasAssociation(self::DEFAULT_CHANNEL_FIELD_NAME)
221
            && $metadata->getAssociationTargetClass(self::DEFAULT_CHANNEL_FIELD_NAME) === self::CHANNEL_ENTITY_CLASS
222
        ) {
223
            return self::DEFAULT_CHANNEL_FIELD_NAME;
224
        }
225
226
        $channelAssociations = $metadata->getAssociationsByTargetClass(self::CHANNEL_ENTITY_CLASS);
227
        foreach ($channelAssociations as $fieldName => $mapping) {
228
            if ($mapping['type'] === ClassMetadata::MANY_TO_ONE && $mapping['isOwningSide']) {
229
                return $fieldName;
230
            }
231
        }
232
233
        throw new \RuntimeException(
234
            sprintf(
235
                'The entity "%s" must have many-to-one relation to "%s".',
236
                $customerClass,
237
                self::CHANNEL_ENTITY_CLASS
238
            )
239
        );
240
    }
241
242
    /**
243
     * Gets all class names for all the Customer entities
244
     *
245
     * @return string[]
246
     */
247
    protected function getCustomerEntities()
248
    {
249
        return array_map(
250
            function (ClassMetadata $metadata) {
251
                return $metadata->name;
252
            },
253
            array_filter(
254
                $this->getObjectManager()->getMetadataFactory()->getAllMetadata(),
255
                function (ClassMetadata $metadata) {
256
                    // @todo: should be removed in CRM-3263
257
                    if ($metadata->name === 'OroCRM\Bundle\ChannelBundle\Entity\CustomerIdentity') {
258
                        return false;
259
                    }
260
261
                    return
262
                        !$metadata->isMappedSuperclass
263
                        && $metadata->getReflectionClass()->isSubclassOf(self::CUSTOMER_IDENTITY_INTERFACE);
264
                }
265
            )
266
        );
267
    }
268
269
    /**
270
     * Returns search aliases for all the Customer entities
271
     *
272
     * @return string[]
273
     */
274
    protected function getCustomerSearchAliases()
275
    {
276
        return array_values(
277
            $this->searchIndexer->getEntityAliases($this->getCustomerEntities())
278
        );
279
    }
280
}
281