StringMatchTableSearchStrategy::buildBaseQuery()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 20
cp 0
rs 9.536
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 12
1
<?php declare(strict_types=1);
2
3
/**
4
 * @copyright   (c) 2017-present brian ridley
5
 * @author      brian ridley <[email protected]>
6
 * @license     http://opensource.org/licenses/MIT MIT
7
 */
8
9
namespace ptlis\GrepDb\Search\Strategy;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\Query\QueryBuilder;
13
use ptlis\GrepDb\Metadata\MySQL\TableMetadata;
14
use ptlis\GrepDb\Search\Result\FieldSearchResult;
15
use ptlis\GrepDb\Search\Result\RowSearchResult;
16
17
/**
18
 * Search strategy for simple string searches.
19
 */
20
final class StringMatchTableSearchStrategy implements TableSearchStrategy
21
{
22
    /**
23
     * @inheritdoc
24
     */
25
    public function getCount(
26
        Connection $connection,
27
        TableMetadata $tableMetadata,
28
        string $searchTerm
29
    ): int {
30
        $statement = $this
31
            ->buildBaseQuery($connection, $tableMetadata, $searchTerm)
32
            ->select('COUNT(*) AS count')
33
            ->execute();
34
35
        return intval($statement->fetchColumn(0));
36
    }
37
38
    /**
39
     * @inheritdoc
40
     */
41
    public function getMatches(
42
        Connection $connection,
43
        TableMetadata $tableMetadata,
44
        string $searchTerm
45
    ): \Generator {
46
47
        // Build lookup list (string columns and primary key if present)
48
        $pkColumnMetadata = $tableMetadata->getPrimaryKeyMetadata();
49
        $lookupColumnsList = $this->getSearchableColumnNames($tableMetadata);
50
        if ($pkColumnMetadata) {
51
            $lookupColumnsList[] = $pkColumnMetadata->getColumnName();
52
        }
53
54
        // Build and execute lookup query
55
        $statement = $this
56
            ->buildBaseQuery($connection, $tableMetadata, $searchTerm)
57
            ->select(array_map(
58
                function (string $columnName) {
59
                    return '`' . $columnName . '`';
60
                },
61
                $lookupColumnsList
62
            ))
63
            ->execute();
64
65
        // Read data one row at a time, building and yielding a RowResult. This lets us deal with large tables without
66
        // a ballooning memory requirement
67
        while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
68
            // Build a list of columns that have matches
69
            $fieldMatchList = [];
70
            foreach ($row as $columnName => $value) {
71
                if (null !== $value && false !== stristr($value, $searchTerm)) {
72
                    $fieldMatchList[] = new FieldSearchResult(
73
                        $tableMetadata->getColumnMetadata($columnName),
74
                        $row[$columnName]
75
                    );
76
                }
77
            }
78
79
            // Handle presence or absence of primary key
80
            $pkValue = $pkColumnMetadata ? $row[$pkColumnMetadata->getColumnName()] : null;
81
            yield new RowSearchResult($tableMetadata, $fieldMatchList, $pkColumnMetadata, $pkValue);
82
        }
83
    }
84
85
    /**
86
     * Build the base query (everything but the SELECT).
87
     */
88
    private function buildBaseQuery(
89
        Connection $connection,
90
        TableMetadata $tableMetadata,
91
        string $searchTerm
92
    ): QueryBuilder {
93
94
        // Build query except WHERE clause
95
        $queryBuilder = $connection
96
            ->createQueryBuilder()
97
            ->select('COUNT(*) AS count')
98
            ->from('`' . $tableMetadata->getDatabaseName() . '`.`' . $tableMetadata->getTableName() . '`')
99
            ->setParameter('search_term', '%' . $searchTerm . '%');
100
101
        foreach ($this->getSearchableColumnNames($tableMetadata) as $index => $columnName) {
102
            $clause = '`' . $columnName . '` LIKE :search_term';
103
            if (0 === $index) {
104
                $queryBuilder->where($clause);
105
            } else {
106
                $queryBuilder->orWhere($clause);
107
            }
108
        }
109
110
        return $queryBuilder;
111
    }
112
113
    /**
114
     * Returns an array of searchable columns (columns that store strings).
115
     *
116
     * @param TableMetadata $tableMetadata
117
     * @return string[]
118
     */
119
    private function getSearchableColumnNames(TableMetadata $tableMetadata): array
120
    {
121
        $columnNameList = [];
122
        foreach ($tableMetadata->getAllColumnMetadata() as $columnMetadata) {
123
            if ($columnMetadata->isStringType()) {
124
                $columnNameList[] = $columnMetadata->getColumnName();
125
            }
126
        }
127
        return $columnNameList;
128
    }
129
}