Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
38 / 40
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Service
95.00% covered (success)
95.00%
38 / 40
83.33% covered (warning)
83.33%
5 / 6
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 invoke
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 resolve
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
5.01
 triggerError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 triggerPre
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 triggerPost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Search service.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010, 2022.
9 * Copyright (C) The National Library of Finland 2019.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  Search
26 * @author   David Maus <maus@hab.de>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @author   Aleksi Peebles <aleksi.peebles@helsinki.fi>
29 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
30 * @link     https://vufind.org
31 */
32
33namespace VuFindSearch;
34
35use Laminas\EventManager\EventManagerInterface;
36use VuFindSearch\Backend\BackendInterface;
37use VuFindSearch\Backend\Exception\BackendException;
38use VuFindSearch\Command\CommandInterface;
39
40/**
41 * Search service.
42 *
43 * @category VuFind
44 * @package  Search
45 * @author   David Maus <maus@hab.de>
46 * @author   Ere Maijala <ere.maijala@helsinki.fi>
47 * @author   Aleksi Peebles <aleksi.peebles@helsinki.fi>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org
50 */
51class Service
52{
53    use \VuFindSearch\Feature\SearchBackendEventManagerTrait;
54
55    /**
56     * Event identifiers.
57     *
58     * @var string
59     */
60    public const EVENT_PRE     = 'pre';
61    public const EVENT_POST    = 'post';
62    public const EVENT_ERROR   = 'error';
63    public const EVENT_RESOLVE = 'resolve';
64
65    /**
66     * Cache resolved backends.
67     *
68     * @var array
69     */
70    protected $backends;
71
72    /**
73     * Constructor.
74     *
75     * @param ?EventManagerInterface $events Event manager (optional)
76     *
77     * @return void
78     */
79    public function __construct(EventManagerInterface $events = null)
80    {
81        if (null !== $events) {
82            $this->setEventManager($events);
83        }
84        $this->backends = [];
85    }
86
87    /**
88     * Invoke a command.
89     *
90     * @param CommandInterface $command Command
91     *
92     * @return CommandInterface
93     */
94    public function invoke(CommandInterface $command)
95    {
96        // The backend instance is no longer added as an event parameter.
97        // All other legacy event parameters are accessible via the command object.
98        $args = ['command' => $command];
99
100        $backend = $this->resolve($command->getTargetIdentifier(), $args);
101
102        $this->triggerPre($this, $args);
103        try {
104            $command->execute($backend);
105        } catch (BackendException $e) {
106            $args['error'] = $e;
107            $this->triggerError($this, $args);
108            throw $e;
109        }
110        $this->triggerPost($this, $args);
111
112        return $command;
113    }
114
115    /**
116     * Resolve a backend.
117     *
118     * @param string            $backendId Backend name
119     * @param array|ArrayAccess $args      Service function arguments
120     *
121     * @return BackendInterface
122     *
123     * @throws Exception\RuntimeException Unable to resolve backend
124     */
125    protected function resolve($backendId, $args)
126    {
127        if (!isset($this->backends[$backendId])) {
128            $response = $this->getEventManager()->triggerUntil(
129                function ($o) {
130                    return $o instanceof BackendInterface;
131                },
132                self::EVENT_RESOLVE,
133                $this,
134                $args
135            );
136            if (!$response->stopped()) {
137                // We need to construct our error message differently depending
138                // on whether or not we have a command object...
139                $context = isset($args['command'])
140                    ? $args['command']->getContext()
141                    : ($args['context'] ?? 'null');
142                $backendId = isset($args['command'])
143                    ? $args['command']->getTargetIdentifier()
144                    : ($args['backend'] ?? $backendId);
145                throw new Exception\RuntimeException(
146                    sprintf(
147                        'Unable to resolve backend: %s, %s',
148                        $context,
149                        $backendId
150                    )
151                );
152            }
153            $this->backends[$backendId] = $response->last();
154        }
155        return $this->backends[$backendId];
156    }
157
158    /**
159     * Trigger the error event.
160     *
161     * @param mixed $target Service instance, or error exception for deprecated
162     *                      legacy events
163     * @param array $args   Event arguments
164     *
165     * @return void
166     */
167    public function triggerError($target, $args)
168    {
169        $this->getEventManager()->trigger(self::EVENT_ERROR, $target, $args);
170    }
171
172    /**
173     * Trigger the pre event.
174     *
175     * @param mixed $target Service instance, or backend instance for deprecated
176     *                      legacy events
177     * @param array $args   Event arguments
178     *
179     * @return void
180     */
181    protected function triggerPre($target, $args)
182    {
183        $this->getEventManager()->trigger(self::EVENT_PRE, $target, $args);
184    }
185
186    /**
187     * Trigger the post event.
188     *
189     * @param mixed $target Service instance, or backend response for deprecated
190     *                      legacy events
191     * @param array $args   Event arguments
192     *
193     * @return void
194     */
195    protected function triggerPost($target, $args)
196    {
197        $this->getEventManager()->trigger(self::EVENT_POST, $target, $args);
198    }
199}