Completed
Push — 1.9 ( d8eb28 )
by
unknown
35:42
created

CalculateAnalyticsCommand::getStateManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace OroCRM\Bundle\AnalyticsBundle\Command;
4
5
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
6
use Symfony\Component\Console\Helper\FormatterHelper;
7
use Symfony\Component\Console\Helper\ProgressHelper;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
12
use Oro\Bundle\BatchBundle\ORM\Query\BufferedQueryResultIterator;
13
use Oro\Bundle\CronBundle\Command\CronCommandInterface;
14
use Oro\Bundle\EntityBundle\ORM\DoctrineHelper;
15
use OroCRM\Bundle\AnalyticsBundle\Builder\AnalyticsBuilder;
16
use OroCRM\Bundle\ChannelBundle\Entity\Channel;
17
use OroCRM\Bundle\ChannelBundle\Entity\CustomerIdentity;
18
use OroCRM\Bundle\AnalyticsBundle\Model\StateManager;
19
20
class CalculateAnalyticsCommand extends ContainerAwareCommand implements CronCommandInterface
21
{
22
    const BATCH_SIZE   = 200;
23
    const COMMAND_NAME = 'oro:cron:analytic:calculate';
24
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public function getDefaultDefinition()
29
    {
30
        return '0 0 * * *';
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    protected function configure()
37
    {
38
        $this
39
            ->setName(self::COMMAND_NAME)
40
            ->setDescription('Calculate all registered analytic metrics')
41
            ->addOption('channel', null, InputOption::VALUE_OPTIONAL, 'Data Channel id to process')
42
            ->addOption(
43
                'ids',
44
                null,
45
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
46
                'Customer identity ids for given channel'
47
            );
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function execute(InputInterface $input, OutputInterface $output)
54
    {
55
        /** @var FormatterHelper $formatter */
56
        $formatter = $this->getHelper('formatter');
57
        /** @var ProgressHelper $progress */
58
        $progress = $this->getHelper('progress');
59
60
        $channel = $input->getOption('channel');
61
        $ids = $input->getOption('ids');
62
63
        if (!$channel && $ids) {
64
            $output->writeln('<error>Option "ids" does not work without "channel"</error>');
65
66
            return;
67
        }
68
69
        if ($this->getStateManager()->isJobRunning()) {
70
            $output->writeln('<error>Job already running. Terminating....</error>');
71
72
            return;
73
        }
74
75
        if ($channel && !$ids && $this->getStateManager()->isJobRunning(sprintf('--channel=%s', $channel))) {
76
            $output->writeln('<error>Job already running. Terminating....</error>');
77
78
            return;
79
        }
80
81
        $channels = $this->getChannels($channel);
82
        foreach ($channels as $channel) {
83
            $output->writeln($formatter->formatSection('Process', sprintf('Channel: %s', $channel->getName())));
84
85
            $entities = $this->getEntitiesByChannel($channel, $ids);
86
87
            if ($input->isInteractive()) {
88
                $progress->start($output, $entities->count());
89
            }
90
91
            $count = $this->processChannel($channel, $entities, $input, $progress);
92
93
            if ($input->isInteractive()) {
94
                $progress->finish();
95
            }
96
            $output->writeln($formatter->formatSection('Done', sprintf('%s/%s updated.', $count, $entities->count())));
97
        }
98
    }
99
100
    /**
101
     * @param Channel                     $channel
102
     * @param BufferedQueryResultIterator $entities
103
     * @param InputInterface              $input
104
     * @param ProgressHelper              $progress
105
     *
106
     * @return int
107
     */
108
    protected function processChannel(
109
        $channel,
110
        BufferedQueryResultIterator $entities,
111
        InputInterface $input,
112
        ProgressHelper $progress
113
    ) {
114
        $count = 0;
115
        $identityFQCN = $channel->getCustomerIdentity();
116
117
        $em = $this->getDoctrineHelper()->getEntityManager($identityFQCN);
118
119
        foreach ($entities as $k => $entity) {
120
            if ($input->isInteractive()) {
121
                $progress->advance();
122
            }
123
124
            if ($this->getAnalyticBuilder()->build($entity)) {
125
                $count++;
126
            }
127
128
            if (($k + 1) % self::BATCH_SIZE === 0) {
129
                $em->flush();
130
                $em->clear();
131
            }
132
        }
133
134
        $em->flush();
135
        $em->clear();
136
137
        return $count;
138
    }
139
140
    /**
141
     * @param int $channelId
142
     *
143
     * @return BufferedQueryResultIterator|Channel[]
144
     */
145
    protected function getChannels($channelId = null)
146
    {
147
        $className = $this->getContainer()->getParameter('orocrm_channel.entity.class');
148
        $qb        = $this->getDoctrineHelper()->getEntityRepository($className)
149
            ->createQueryBuilder('c');
150
151
        $qb->orderBy('c.id');
152
        $qb->andWhere('c.status = :status');
153
        $qb->setParameter('status', Channel::STATUS_ACTIVE);
154
155
        if ($channelId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $channelId of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
156
            $qb->andWhere('c.id = :id');
157
            $qb->setParameter('id', $channelId);
158
        }
159
160
        $analyticsInterface = $this->getContainer()->getParameter('orocrm_analytics.model.analytics_aware_interface');
161
162
        return new \CallbackFilterIterator(
163
            new BufferedQueryResultIterator($qb),
164
            function (Channel $channel) use ($analyticsInterface) {
165
                $identityFQCN = $channel->getCustomerIdentity();
166
167
                return is_a($identityFQCN, $analyticsInterface, true);
168
            }
169
        );
170
    }
171
172
    /**
173
     * @param Channel $channel
174
     * @param array   $ids
175
     *
176
     * @return BufferedQueryResultIterator|CustomerIdentity[]
177
     */
178
    protected function getEntitiesByChannel(Channel $channel, array $ids = [])
179
    {
180
        $entityFQCN = $channel->getCustomerIdentity();
181
182
        $qb = $this->getDoctrineHelper()->getEntityRepository($entityFQCN)
183
            ->createQueryBuilder('e');
184
185
        $qb->orderBy(sprintf('e.%s', $this->getDoctrineHelper()->getSingleEntityIdentifierFieldName($entityFQCN)));
186
        $qb->andWhere('e.dataChannel = :dataChannel');
187
        $qb->setParameter('dataChannel', $channel);
188
189
        if ($ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
190
            $qb->andWhere($qb->expr()->in('e.id', ':ids'));
191
            $qb->setParameter('ids', $ids);
192
        }
193
194
        $iterator = new BufferedQueryResultIterator($qb);
195
        // !!! should be the same as flush batch, will not work otherwise because of detached entities after EM#clear()
196
        $iterator->setBufferSize(self::BATCH_SIZE);
197
198
        return $iterator;
199
    }
200
201
    /**
202
     * @return AnalyticsBuilder
203
     */
204
    protected function getAnalyticBuilder()
205
    {
206
        return $this->getContainer()->get('orocrm_analytics.builder');
207
    }
208
209
    /**
210
     * @return DoctrineHelper
211
     */
212
    protected function getDoctrineHelper()
213
    {
214
        return $this->getContainer()->get('oro_entity.doctrine_helper');
215
    }
216
217
    /**
218
     * @return StateManager
219
     */
220
    protected function getStateManager()
221
    {
222
        return $this->getContainer()->get('orocrm_analytics.model.state_manager');
223
    }
224
}
225