SearchPackageController   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 11
lcom 1
cbo 12
dl 0
loc 291
rs 10
c 6
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
B searchAction() 0 34 4
A getFilters() 0 13 2
A getInstalledVersion() 0 9 2
B getRepositorySearch() 0 29 3
1
<?php
2
3
/**
4
 * This file is part of tenside/core-bundle.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core-bundle
14
 * @author     Christian Schiffler <[email protected]>
15
 * @author     Andreas Schempp <[email protected]>
16
 * @copyright  2015 Christian Schiffler <[email protected]>
17
 * @license    https://github.com/tenside/core-bundle/blob/master/LICENSE MIT
18
 * @link       https://github.com/tenside/core-bundle
19
 * @filesource
20
 */
21
22
namespace Tenside\CoreBundle\Controller;
23
24
use Composer\Composer;
25
use Composer\Package\PackageInterface;
26
use Composer\Repository\CompositeRepository;
27
use Composer\Repository\RepositoryInterface;
28
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
29
use Symfony\Component\HttpFoundation\JsonResponse;
30
use Symfony\Component\HttpFoundation\Request;
31
use Tenside\Core\Composer\Package\VersionedPackage;
32
use Tenside\Core\Composer\PackageConverter;
33
use Tenside\Core\Composer\Search\CompositeSearch;
34
use Tenside\Core\Composer\Search\RepositorySearch;
35
use Tenside\Core\Util\JsonArray;
36
use Tenside\CoreBundle\Annotation\ApiDescription;
37
38
/**
39
 * List and manipulate the installed packages.
40
 */
41
class SearchPackageController extends AbstractController
42
{
43
    /**
44
     * Search for packages.
45
     *
46
     * @param Request $request The search request.
47
     *
48
     * @return JsonResponse
49
     *
50
     * @ApiDoc(
51
     *   section="search",
52
     *   statusCodes = {
53
     *     200 = "When everything worked out ok"
54
     *   },
55
     *   authentication = true,
56
     *   authenticationRoles = {
57
     *     "ROLE_MANIPULATE_REQUIREMENTS"
58
     *   }
59
     * )
60
     * @ApiDescription(
61
     *   request={
62
     *    "keywords" = {
63
     *      "dataType" = "string",
64
     *      "description" = "The name of the project to search or any other keyword.",
65
     *      "required" = true
66
     *    },
67
     *    "type" = {
68
     *      "dataType" = "choice",
69
     *      "description" = "The type of package to search (optional, default: all).",
70
     *      "format" = "['installed', 'contao', 'all']",
71
     *      "required" = false
72
     *    },
73
     *    "threshold" = {
74
     *      "dataType" = "int",
75
     *      "description" = "The amount of results after which the search shall be stopped (optional, default: 20).",
76
     *      "required" = false
77
     *    }
78
     *   },
79
     *   response={
80
     *     "package name 1...n" = {
81
     *       "actualType" = "object",
82
     *       "subType" = "object",
83
     *       "description" = "The content of the packages",
84
     *       "children" = {
85
     *         "name" = {
86
     *           "dataType" = "string",
87
     *           "description" = "The name of the package"
88
     *         },
89
     *         "version" = {
90
     *           "dataType" = "string",
91
     *           "description" = "The version of the package"
92
     *         },
93
     *         "constraint" = {
94
     *           "dataType" = "string",
95
     *           "description" = "The constraint of the package (when package is installed)"
96
     *         },
97
     *         "type" = {
98
     *           "dataType" = "string",
99
     *           "description" = "The noted package type"
100
     *         },
101
     *         "locked" = {
102
     *           "dataType" = "string",
103
     *           "description" = "Flag if the package has been locked for updates"
104
     *         },
105
     *         "time" = {
106
     *           "dataType" = "datetime",
107
     *           "description" = "The release date"
108
     *         },
109
     *         "upgrade_version" = {
110
     *           "dataType" = "string",
111
     *           "description" = "The version available for upgrade (optional, if any)"
112
     *         },
113
     *         "description" = {
114
     *           "dataType" = "string",
115
     *           "description" = "The package description"
116
     *         },
117
     *         "license" = {
118
     *           "actualType" = "collection",
119
     *           "subType" = "string",
120
     *           "description" = "The licenses"
121
     *         },
122
     *         "keywords" = {
123
     *           "actualType" = "collection",
124
     *           "subType" = "string",
125
     *           "description" = "The keywords"
126
     *         },
127
     *         "homepage" = {
128
     *           "dataType" = "string",
129
     *           "description" = "The support website (optional, if any)"
130
     *         },
131
     *         "authors" = {
132
     *           "actualType" = "collection",
133
     *           "subType" = "object",
134
     *           "description" = "The authors",
135
     *           "children" = {
136
     *             "name" = {
137
     *               "dataType" = "string",
138
     *               "description" = "Full name of the author (optional, if any)"
139
     *             },
140
     *             "homepage" = {
141
     *               "dataType" = "string",
142
     *               "description" = "Email address of the author (optional, if any)"
143
     *             },
144
     *             "email" = {
145
     *               "dataType" = "string",
146
     *               "description" = "Homepage URL for the author (optional, if any)"
147
     *             },
148
     *             "role" = {
149
     *               "dataType" = "string",
150
     *               "description" = "Author's role in the project (optional, if any)"
151
     *             }
152
     *           }
153
     *         },
154
     *         "support" = {
155
     *           "actualType" = "collection",
156
     *           "subType" = "object",
157
     *           "description" = "The support options",
158
     *           "children" = {
159
     *             "email" = {
160
     *               "dataType" = "string",
161
     *               "description" = "Email address for support (optional, if any)"
162
     *             },
163
     *             "issues" = {
164
     *               "dataType" = "string",
165
     *               "description" = "URL to the issue tracker (optional, if any)"
166
     *             },
167
     *             "forum" = {
168
     *               "dataType" = "string",
169
     *               "description" = "URL to the forum (optional, if any)"
170
     *             },
171
     *             "wiki" = {
172
     *               "dataType" = "string",
173
     *               "description" = "URL to the wiki (optional, if any)"
174
     *             },
175
     *             "irc" = {
176
     *               "dataType" = "string",
177
     *               "description" = "IRC channel for support, as irc://server/channel (optional, if any)"
178
     *             },
179
     *             "source" = {
180
     *               "dataType" = "string",
181
     *               "description" = "URL to browse or download the sources (optional, if any)"
182
     *             },
183
     *             "docs" = {
184
     *               "dataType" = "string",
185
     *               "description" = "URL to the documentation (optional, if any)"
186
     *             },
187
     *           }
188
     *         },
189
     *         "abandoned" = {
190
     *           "dataType" = "boolean",
191
     *           "description" = "Flag if this package is abandoned"
192
     *         },
193
     *         "replacement" = {
194
     *           "dataType" = "string",
195
     *           "description" = "Replacement for this package (optional, if any)"
196
     *         },
197
     *         "installed" = {
198
     *           "dataType" = "int",
199
     *           "description" = "Amount of installations"
200
     *         },
201
     *         "downloads" = {
202
     *           "dataType" = "int",
203
     *           "description" = "Amount of downloads"
204
     *         },
205
     *         "favers" = {
206
     *           "dataType" = "int",
207
     *           "description" = "Amount of favers"
208
     *         },
209
     *       }
210
     *     }
211
     *   }
212
     * )
213
     */
214
    public function searchAction(Request $request)
215
    {
216
        $composer        = $this->getComposer();
217
        $data            = new JsonArray($request->getContent());
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, Tenside\Core\Util\JsonArray::__construct() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
218
        $keywords        = $data->get('keywords');
219
        $type            = $data->get('type');
220
        $threshold       = $data->has('threshold') ? $data->get('threshold') : 20;
221
        $localRepository = $composer->getRepositoryManager()->getLocalRepository();
222
        $searcher        = $this->getRepositorySearch($keywords, $type, $composer, $threshold);
223
        $results         = $searcher->searchAndDecorate($keywords, $this->getFilters($type));
224
        $responseData    = [];
225
        $rootPackage     = $composer->getPackage();
226
        $converter       = new PackageConverter($rootPackage);
227
228
        foreach ($results as $versionedResult) {
229
            /** @var VersionedPackage $versionedResult */
230
231
            // Might have no version matching the current stability setting.
232
            if (null === ($latestVersion = $versionedResult->getLatestVersion())) {
233
                continue;
234
            }
235
236
            $package = $converter->convertPackageToArray($latestVersion);
237
            $package
238
                ->set('installed', $this->getInstalledVersion($localRepository, $versionedResult->getName()))
239
                ->set('downloads', $versionedResult->getMetaData('downloads'))
240
                ->set('favers', $versionedResult->getMetaData('favers'));
241
242
            $responseData[$package->get('name')] = $package->getData();
243
        }
244
245
        return JsonResponse::create($responseData)
246
            ->setEncodingOptions((JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT));
247
    }
248
249
    /**
250
     * Get the array of filter closures.
251
     *
252
     * @param string $type The desired search type (contao, installed or empty).
253
     *
254
     * @return \Closure[]
255
     */
256
    private function getFilters($type)
257
    {
258
        $filters = [];
259
        if ('contao' === $type) {
260
            $filters[] =
261
                function ($package) {
262
                    /** @var PackageInterface $package */
263
                    return in_array($package->getType(), ['contao-module', 'contao-bundle', 'legacy-contao-module']);
264
                };
265
        }
266
267
        return $filters;
268
    }
269
270
    /**
271
     * Retrieve the installed version of a package (if any).
272
     *
273
     * @param RepositoryInterface $localRepository The local repository.
274
     *
275
     * @param string              $packageName     The name of the package to search.
276
     *
277
     * @return null|string
278
     */
279
    private function getInstalledVersion($localRepository, $packageName)
280
    {
281
        if (count($installed = $localRepository->findPackages($packageName))) {
282
            /** @var PackageInterface[] $installed */
283
            return $installed[0]->getPrettyVersion();
284
        }
285
286
        return null;
287
    }
288
289
    /**
290
     * Create a repository search instance.
291
     *
292
     * @param string   $keywords  The search keywords.
293
     *
294
     * @param string   $type      The desired search type.
295
     *
296
     * @param Composer $composer  The composer instance.
297
     *
298
     * @param int      $threshold The threshold after which to stop searching.
299
     *
300
     * @return CompositeSearch
301
     */
302
    private function getRepositorySearch($keywords, $type, Composer $composer, $threshold)
303
    {
304
        $repositoryManager = $composer->getRepositoryManager();
305
        $localRepository   = $repositoryManager->getLocalRepository();
306
307
        $repositories = new CompositeRepository([$localRepository]);
308
309
        // If we do not search locally, add the other repositories now.
310
        if ('installed' !== $type) {
311
            $repositories->addRepository(new CompositeRepository($repositoryManager->getRepositories()));
312
        }
313
314
        $repositorySearch = new RepositorySearch($repositories);
315
        $repositorySearch->setSatisfactionThreshold($threshold);
316
        if (false !== strpos($keywords, '/')) {
317
            $repositorySearch->disableSearchType(RepositoryInterface::SEARCH_FULLTEXT);
318
        } else {
319
            $repositorySearch->disableSearchType(RepositoryInterface::SEARCH_NAME);
320
        }
321
322
        $searcher = new CompositeSearch(
323
            [
324
                $repositorySearch
325
            ]
326
        );
327
        $searcher->setSatisfactionThreshold($threshold);
328
329
        return $searcher;
330
    }
331
}
332