Completed
Push — master ( 0bae06...931bd3 )
by
unknown
78:08
created

CustomerSearchApiEntityManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

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