├── LICENSE ├── README.md ├── composer.json └── src └── InsertOnDuplicateKeyServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Guido Cella 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Insert On Duplicate Key And Insert Ignore 2 | 3 | This package is deprecated because `upsert` and `insertOrIgnore` have been added to Laravel. 4 | 5 | If you need the pivot functions, they are trivial to implement: 6 | 7 | ```php 8 | BelongsToMany::macro('attachUpsert', function ($id, array $attributes = []) { 9 | $this->newPivotStatement()->upsert($this->formatAttachRecords( 10 | $this->parseIds($id), 11 | $attributes 12 | ), null); 13 | }); 14 | 15 | BelongsToMany::macro('attachOrIgnore', function ($id, array $attributes = []) { 16 | $this->newPivotStatement()->insertOrIgnore($this->formatAttachRecords( 17 | $this->parseIds($id), 18 | $attributes 19 | )); 20 | }); 21 | ``` 22 | 23 | This package provides macros to run INSERT ... ON DUPLICATE KEY UPDATE and INSERT IGNORE queries on models and pivot tables with Laravel's ORM Eloquent using MySql or MariaDB. 24 | 25 | ## Installation 26 | 27 | Install this package with composer. 28 | 29 | ```sh 30 | composer require guidocella/eloquent-insert-on-duplicate-key 31 | ``` 32 | 33 | If you don't use Package Auto-Discovery yet add the service provider to your Package Service Providers in `config/app.php`. 34 | 35 | ```php 36 | InsertOnDuplicateKey\InsertOnDuplicateKeyServiceProvider::class, 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### Models 42 | 43 | Call `insertOnDuplicateKey` or `insertIgnore` from a model with the array of data to insert in its table. 44 | 45 | ```php 46 | $data = [ 47 | ['id' => 1, 'name' => 'name1', 'email' => 'user1@email.com'], 48 | ['id' => 2, 'name' => 'name2', 'email' => 'user2@email.com'], 49 | ]; 50 | 51 | User::insertOnDuplicateKey($data); 52 | 53 | User::insertIgnore($data); 54 | ``` 55 | 56 | #### Customizing the ON DUPLICATE KEY UPDATE clause 57 | 58 | ##### Update only certain columns 59 | 60 | If you want to update only certain columns, pass them as the 2nd argument. 61 | 62 | ```php 63 | User::insertOnDuplicateKey([ 64 | 'id' => 1, 65 | 'name' => 'new name', 66 | 'email' => 'foo@gmail.com', 67 | ], ['name']); 68 | // The name will be updated but not the email. 69 | ``` 70 | 71 | ##### Update with custom values 72 | 73 | You can customize the value with which the columns will be updated when a row already exists by passing an associative array. 74 | 75 | In the following example, if a user with id = 1 doesn't exist, it will be created with name = 'created user'. If it already exists, it will be updated with name = 'updated user'. 76 | 77 | ```php 78 | User::insertOnDuplicateKey([ 79 | 'id' => 1, 80 | 'name' => 'created user', 81 | ], ['name' => 'updated user']); 82 | ``` 83 | 84 | The generated SQL is: 85 | 86 | ```sql 87 | INSERT INTO `users` (`id`, `name`) VALUES (1, "created user") ON DUPLICATE KEY UPDATE `name` = "updated user" 88 | ``` 89 | 90 | You may combine key/value pairs and column names in the 2nd argument to specify the columns to update with a custom literal or expression or with the default `VALUES(column)`. For example: 91 | 92 | ```php 93 | User::insertOnDuplicateKey([ 94 | 'id' => 1, 95 | 'name' => 'created user', 96 | 'email' => 'new@gmail.com', 97 | 'password' => 'secret', 98 | ], ['name' => 'updated user', 'email']); 99 | ``` 100 | 101 | will generate 102 | 103 | ```sql 104 | INSERT INTO `users` (`id`, `name`, `email`, `password`) 105 | VALUES (1, "created user", "new@gmail.com", "secret") 106 | ON DUPLICATE KEY UPDATE `name` = "updated user", `email` = VALUES(`email`) 107 | ``` 108 | 109 | ### Pivot tables 110 | 111 | Call `attachOnDuplicateKey` and `attachIgnore` from a `BelongsToMany` relation to run the inserts in its pivot table. You can pass the data in any of the formats accepted by `attach`. 112 | 113 | ```php 114 | $pivotData = [ 115 | 1 => ['expires_at' => Carbon::today()], 116 | 2 => ['expires_at' => Carbon::tomorrow()], 117 | ]; 118 | 119 | $user->roles()->attachOnDuplicateKey($pivotData); 120 | 121 | $user->roles()->attachIgnore($pivotData); 122 | ``` 123 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guidocella/laravel-insert-on-duplicate-key", 3 | "type": "library", 4 | "description": "Macros for INSERT ON DUPLICATE KEY UPDATE and INSERT IGNORE with Eloquent", 5 | "keywords": ["laravel", "eloquent", "database", "mysql"], 6 | "license": "MIT", 7 | "require": { 8 | "php": ">=5.6.4", 9 | "illuminate/database": "^5.4||^6||^7||^8" 10 | }, 11 | "require-dev": { 12 | "laravel/laravel": "^8", 13 | "phpunit/phpunit": "^9" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "InsertOnDuplicateKey\\": "src" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "InsertOnDuplicateKey\\": "tests" 23 | } 24 | }, 25 | "extra": { 26 | "laravel": { 27 | "providers": [ 28 | "InsertOnDuplicateKey\\InsertOnDuplicateKeyServiceProvider" 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/InsertOnDuplicateKeyServiceProvider.php: -------------------------------------------------------------------------------- 1 | insertOnDuplicateKey($values, null, 'ignore'); 25 | }); 26 | 27 | /** 28 | * Run an insert on duplicate key update statement against the database. 29 | * 30 | * @param array $values 31 | * @param array $columnsToUpdate 32 | * @param string $type 33 | * @return bool 34 | */ 35 | Builder::macro('insertOnDuplicateKey', function ( 36 | array $values, 37 | array $columnsToUpdate = null, 38 | $type = 'on duplicate key' 39 | ) { 40 | // Since every insert gets treated like a batch insert, we will make sure the 41 | // bindings are structured in a way that is convenient for building these 42 | // inserts statements by verifying the elements are actually an array. 43 | if (empty($values)) { 44 | return true; 45 | } 46 | 47 | if (!is_array(reset($values))) { 48 | $values = [$values]; 49 | } 50 | 51 | // Here, we will sort the insert keys for every record so that each insert is 52 | // in the same order for the record. We need to make sure this is the case 53 | // so there are not any errors or problems when inserting these records. 54 | else { 55 | foreach ($values as $key => $value) { 56 | ksort($value); 57 | $values[$key] = $value; 58 | } 59 | } 60 | 61 | // Finally, we will run this query against the database connection and return 62 | // the results. We will need to also flatten these bindings before running 63 | // the query so they are all in one huge, flattened array for execution. 64 | $bindings = $this->cleanBindings(Arr::flatten($values, 1)); 65 | 66 | // Essentially we will force every insert to be treated as a batch insert which 67 | // simply makes creating the SQL easier for us since we can utilize the same 68 | // basic routine regardless of an amount of records given to us to insert. 69 | $table = $this->grammar->wrapTable($this->from); 70 | 71 | $columns = array_keys(reset($values)); 72 | 73 | $columnsString = $this->grammar->columnize($columns); 74 | 75 | // We need to build a list of parameter place-holders of values that are bound 76 | // to the query. Each insert should have the exact same amount of parameter 77 | // bindings so we will loop through the record and parameterize them all. 78 | $parameters = collect($values)->map(function ($record) { 79 | return '(' . $this->grammar->parameterize($record) . ')'; 80 | })->implode(', '); 81 | 82 | $sql = 'insert ' . ($type === 'ignore' ? 'ignore ' : '') . "into $table ($columnsString) values $parameters"; 83 | 84 | if ($type === 'ignore') { 85 | return $this->connection->insert($sql, $bindings); 86 | } 87 | 88 | 89 | $sql .= ' on duplicate key update '; 90 | 91 | // We will update all the columns specified in $values by default. 92 | if ($columnsToUpdate === null) { 93 | $columnsToUpdate = $columns; 94 | } 95 | 96 | foreach ($columnsToUpdate as $key => $value) { 97 | $column = $this->grammar->wrap(is_int($key) ? $value : $key); 98 | 99 | $sql .= "$column = "; 100 | 101 | if (is_int($key)) { 102 | $sql .= "VALUES($column)"; 103 | } elseif ($this->grammar->isExpression($value)) { 104 | $sql .= $value->getValue(); 105 | } else { 106 | $sql .= '?'; 107 | $bindings[] = $value; 108 | } 109 | 110 | $sql .= ','; 111 | } 112 | 113 | return $this->connection->insert(rtrim($sql, ','), $bindings); 114 | }); 115 | 116 | /** 117 | * Attach models to the parent ignoring existing associations. 118 | * 119 | * @param mixed $id 120 | * @param array $attributes 121 | * @return void 122 | */ 123 | BelongsToMany::macro('attachIgnore', function ($id, array $attributes = [], $touch = true) { 124 | $this->newPivotStatement()->insertIgnore($this->formatAttachRecords( 125 | $this->parseIds($id), $attributes 126 | )); 127 | 128 | if ($touch) { 129 | $this->touchIfTouching(); 130 | } 131 | }); 132 | 133 | /** 134 | * Attach models to the parent updating existing associations. 135 | * 136 | * @param mixed $id 137 | * @param array $attributes 138 | * @return void 139 | */ 140 | BelongsToMany::macro('attachOnDuplicateKey', function ($id, array $attributes = [], $touch = true) { 141 | $this->newPivotStatement()->insertOnDuplicateKey($this->formatAttachRecords( 142 | $this->parseIds($id), $attributes 143 | )); 144 | 145 | if ($touch) { 146 | $this->touchIfTouching(); 147 | } 148 | }); 149 | } 150 | } 151 | --------------------------------------------------------------------------------