Completed
Push — 1.9 ( d8eb28...5c3e2e )
by
unknown
61:52 queued 29s
created

getChannelFieldName()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 8.439
cc 6
eloc 14
nc 4
nop 1
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);
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...
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