Completed
Pull Request — master (#356)
by
unknown
10:31
created

MySQLConverter::getTableStr()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * League.Csv (https://csv.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[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
12
declare(strict_types=1);
13
14
namespace League\Csv;
15
16
use Traversable;
17
18
/**
19
 * Converts tabular data into a SQL string.
20
 */
21
class MySQLConverter
22
{
23
    /**
24
     * @var string
25
     */
26
    protected $table = 'csv_data';
27
28
    /**
29
     * @var string
30
     */
31
    protected $database = 'csv_data';
32
33
    /**
34
     * MySQLConverter constructor.
35
     * @param string $database
36
     * @param string $table
37
     */
38
    public function __construct(string $database = '', string $table = '')
39
    {
40
        if (! empty($table)) {
41
            $this->table = $table;
42
        }
43
        if (! empty($database)) {
44
            $this->database = $database;
45
        }
46
    }
47
48
    /**
49
     * @param Traversable $records
50
     * @return string
51
     */
52
    public function convert(Traversable $records): string
53
    {
54
        $sql = $this->generateUseStatement();
55
        $sql .= $this->generateDropStatement();
56
        $sql .= $this->generateCreateStatement($records);
57
        $sql .= $this->generateInsertStatement($records);
58
59
        return $sql;
60
    }
61
62
    /**
63
     * @return string
64
     */
65
    protected function generateUseStatement(): string
66
    {
67
        return 'USE `'.$this->database.'`;'.PHP_EOL;
68
    }
69
70
    /**
71
     * @return string
72
     */
73
    protected function generateDropStatement(): string
74
    {
75
        return 'DROP TABLE IF EXISTS '.$this->getTableStr().';'.PHP_EOL;
76
    }
77
78
    /**
79
     * @param Traversable $records
80
     * @return string
81
     */
82
    protected function generateCreateStatement(Traversable $records): string
83
    {
84
        $fields = $this->prepareColumns($records);
85
        return 'CREATE TABLE '.$this->getTableStr().' ('.PHP_EOL.$fields.PHP_EOL.') ENGINE=innodb;'.PHP_EOL;
86
    }
87
88
    /**
89
     * @param Traversable $records
90
     * @return string
91
     */
92
    protected function generateInsertStatement(Traversable $records): string
93
    {
94
        $records = $this->prepareRecords($records);
95
        return $records;
96
    }
97
98
    /**
99
     * @return string
100
     */
101
    protected function getTableStr(): string
102
    {
103
        return '`'.$this->database.'`.`'.$this->table.'`';
104
    }
105
106
    /**
107
     * @param Traversable $records
108
     * @return string
109
     */
110
    protected function prepareRecords(Traversable $records): string
111
    {
112
        $values = [];
113
        foreach ($records as $row) {
114
            $fields = [];
115
            foreach ($row as $data) {
116
                $data = trim($data);
117
                if (empty($data)) {
118
                    $fields[] = 'NULL';
119
                } else {
120
                    $fields[] = "'".trim($data)."'";
121
                }
122
            }
123
            $fields = 'INSERT INTO '.$this->getTableStr().' VALUES ('.implode(',', $fields).');';
124
            $values[] = $fields;
125
        }
126
        $values = implode(PHP_EOL, $values);
127
        return $values;
128
    }
129
130
131
    /**
132
     * @param Traversable $records
133
     * @return string
134
     */
135
    protected function prepareColumns(Traversable $records): string
136
    {
137
        $records->rewind();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method rewind() does only exist in the following implementations of said interface: APCUIterator, AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, League\Csv\MapIterator, League\Csv\Stream, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, Nette\Iterators\CachingIterator, Nette\Iterators\Mapper, NoRewindIterator, PHPUnit\Framework\TestSuiteIterator, PHPUnit\Runner\Filter\ExcludeGroupFilterIterator, PHPUnit\Runner\Filter\GroupFilterIterator, PHPUnit\Runner\Filter\IncludeGroupFilterIterator, PHPUnit\Runner\Filter\NameFilterIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, PharIo\Manifest\AuthorCollectionIterator, PharIo\Manifest\AuthorElementCollection, PharIo\Manifest\BundledComponentCollectionIterator, PharIo\Manifest\ComponentElementCollection, PharIo\Manifest\ElementCollection, PharIo\Manifest\ExtElementCollection, PharIo\Manifest\RequirementCollectionIterator, PhpCsFixer\Doctrine\Annotation\Tokens, PhpCsFixer\Runner\FileCachingLintingIterator, PhpCsFixer\Runner\FileFilterIterator, PhpCsFixer\Runner\FileLintingIterator, PhpCsFixer\Tokenizer\Tokens, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2, TheSeer\Tokenizer\TokenCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
138
        $sample_data = $records->current();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method current() does only exist in the following implementations of said interface: APCUIterator, AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, League\Csv\MapIterator, League\Csv\Stream, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, Nette\Iterators\CachingIterator, Nette\Iterators\Mapper, NoRewindIterator, PHPUnit\Framework\TestSuiteIterator, PHPUnit\Runner\Filter\ExcludeGroupFilterIterator, PHPUnit\Runner\Filter\GroupFilterIterator, PHPUnit\Runner\Filter\IncludeGroupFilterIterator, PHPUnit\Runner\Filter\NameFilterIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, PharIo\Manifest\AuthorCollectionIterator, PharIo\Manifest\AuthorElementCollection, PharIo\Manifest\BundledComponentCollectionIterator, PharIo\Manifest\ComponentElementCollection, PharIo\Manifest\ElementCollection, PharIo\Manifest\ExtElementCollection, PharIo\Manifest\RequirementCollectionIterator, PhpCsFixer\Doctrine\Annotation\Tokens, PhpCsFixer\Runner\FileCachingLintingIterator, PhpCsFixer\Runner\FileFilterIterator, PhpCsFixer\Runner\FileLintingIterator, PhpCsFixer\Tokenizer\Tokens, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2, TheSeer\Tokenizer\TokenCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
139
        $columns = array_keys($sample_data);
140
        $fields = [];
141
        foreach ($columns as $column) {
142
            $column_name = strtolower(iconv('UTF-8', 'ASCII//TRANSLIT', $column));
143
            $column_name = preg_replace('/[ ]+/', ' ', $column_name);
144
            $column_name = str_replace(' ', '_', $column_name);
145
            $data = trim($sample_data[$column]);
146
147
            $is_int = filter_var($data, FILTER_VALIDATE_INT);
148
            if ($is_int !== false) {
149
                if ($is_int < 2147483647) {
150
                    $fields[] = '`'.$column_name.'` INT NULL';
151
                } else {
152
                    $fields[] = '`'.$column_name.'` BIGINT NULL';
153
                }
154
155
                continue;
156
            }
157
158
            $is_float = filter_var($data, FILTER_VALIDATE_FLOAT);
159
            if ($is_float !== false) {
160
                if ($is_float < 2147483647) {
161
                    $fields[] = '`'.$column_name.'` FLOAT(10,24) NULL';
162
                } else {
163
                    $fields[] = '`'.$column_name.'` DOUBLE(16,53) NULL';
164
                }
165
166
                continue;
167
            }
168
169
            if (strlen($data) < 255) {
170
                $fields[] = '`'.$column_name.'` VARCHAR(255) NULL';
171
            } else {
172
                $fields[] = '`'.$column_name.'` LONGTEXT NULL';
173
            }
174
        }
175
176
        $fields = implode(','.PHP_EOL, $fields);
177
        return $fields;
178
    }
179
}
180