Skip to content

Commit

Permalink
Update document and update Model
Browse files Browse the repository at this point in the history
update Model
  • Loading branch information
alvin0 committed Apr 2, 2023
1 parent 3dc0365 commit 02abdf7
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 49 deletions.
125 changes: 114 additions & 11 deletions README.md
Expand Up @@ -9,24 +9,21 @@ Redis is not the place to store complex relational data and Redis emphasizes its

### Laravel version supports

| Laravel | Is Support |
| Redis Version | Laravel version(s) |
| :---: | :---: |
| < 8 | No |
| 8 | Yes |
| 9 | Yes |
| 10 | Yes |
| 0.x | 8 / 9 / 10 |

### Model Supports

| Function | Is Working |
| Method | Is Working |
| --- | :---: |
| CURD | Yes |
| Condition Select | Yes |
| Chunking | Yes |
| Transaction | Yes |
| Insert a lot of data | Yes |
| Delete a lot of data | Yes |
| Update a lot of data | comming soon |
| Update a lot of data | Think.. |
| Relationship | No |

### Key structure
Expand Down Expand Up @@ -74,9 +71,43 @@ php artisan redis-model:model User
## Model Conventions
### Primary Keys, Sub-Keys And Fillable

Primary Keys are properties that are not assigned by default, and they determine the search behavior of the model. Make sure that the values of primary keys are unique to avoid confusion when retrieving data based on conditions.
The primary key is an attribute assigned by default by uuid, and they determine the search behavior of the model.
Make sure that the values of primary keys are unique to avoid confusion when retrieving data based on conditions.
Sub keys are keys that can be duplicated, and they will help you search using the where method in the model.
To declare attributes for a model, you need to declare them in the $fillable variable. You should also declare the primary key and subkeys in this variable.
To declare attributes for a model, you need to declare them in the `fillable` variable. You should also declare the `primaryKey` and `subKeys` in this variable.

>
```php
use Alvin0\RedisModel\Model;

class User extends Model {

/**
* The model's sub keys for the model.
*
* @var array
*/
protected $subKeys = [
'name',
'role',
];

/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'id',
'email',
'name',
'role',
'address'
];
}
```

> If possible, please turn off the automatic UUID generation feature for model keys and choose keys for data to ensure easy data search with keys. Make sure the primary key is unique for the model.
```php
use Alvin0\RedisModel\Model;
Expand All @@ -88,6 +119,13 @@ class User extends Model {
* @var bool
*/
protected $primaryKey = 'email';

/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;

/**
* The model's sub keys for the model.
Expand All @@ -112,6 +150,7 @@ class User extends Model {
];
}
```

### Table Names
So, in this case, RedisModel will assume the `User` model stores records in the `users` table.

Expand Down Expand Up @@ -306,8 +345,7 @@ $user->email //email@gmail.com
```

### Insert Statements
To solve the issue of inserting multiple items into a table, you can use the inserts function. It will perform the insert within a transaction to ensure a rollback in case of errors that you may not be aware of. It is recommended to use array chunk to ensure performance.

To solve the issue of inserting multiple items into a table, you can use the inserts function. It is recommended to use array chunk to ensure performance.
```php
use App\RedisModels\User;
use Illuminate\Support\Facades\Hash;
Expand All @@ -330,6 +368,31 @@ $seed = function ($limit) {
User::insert($seed(10));
```

> I think you should use the transaction method to ensure that your data will be rolled back if an error occurs during the process of inserting multiple data.
```php
use App\RedisModels\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

$seed = function ($limit) {
$users = [];
for ($i = 0; $i < $limit; $i++) {
$users[] = [
'email' => Str::random(10) . '@gmail.com',
'name' => Str::random(8),
'token' => md5(Str::random(10)),
'expire_at' => now(),
];
}

return $users;
};

User::transaction(function ($conTransaction) use ($data) {
User::insert($seed(10), $conTransaction);
});
```

### Update Model
The `save` method may also be used to update models that already exist in the database. To update a model, you should retrieve it and set any attributes you wish to update. Then, you should call the model's `save` method. Again, the `updated_at` timestamp will automatically be updated, so there is no need to manually set its value:

Expand Down Expand Up @@ -384,3 +447,43 @@ use App\RedisModels\User;

$user = User::find('email@gmail.com')->getExpire(); // The remaining time to live of the instance is 39 seconds.
```

### Transaction

The Redis model's transaction method provides a convenient wrapper around Redis' native `MULTI` and `EXEC` commands. The transaction method accepts a closure as its only argument. This closure will receive a Redis connection instance and may issue any commands it would like to this instance. All of the Redis commands issued within the closure will be executed in a single, atomic transaction:

> When defining a Redis transaction, you may not retrieve any values from the Redis connection. Remember, your transaction is executed as a single, atomic operation and that operation is not executed until your entire closure has finished executing its commands.
```php
use App\RedisModels\User;
use Redis;

$data [
'users:id:1:email:email@example:name:alvin:role:admin' => [
'id' => 1,
'email'=>'email@example'
'name' => 'alvin',
'role'=>'admin'
]
'users:id:2:email:email@example:name:alvin:role:admin' => [
'id' => 2,
'email'=>'email@example'
'name' => 'alvin',
'role'=>'admin'
]
'users:id:3:email:email@example:name:alvin:role:admin' => [
'id' => 3,
'email'=>'email@example'
'name' => 'alvin',
'role'=>'admin'
]
];

$user = User::transaction(function (Redis $conTransaction) use($data) {
foreach($data as $key => $value) {
$conTransaction->hMSet($key, $value);
}
// $conTransaction->discard();
});

```
9 changes: 9 additions & 0 deletions src/Exceptions/ErrorTransactionException.php
@@ -0,0 +1,9 @@
<?php
namespace Alvin0\RedisModel\Exceptions;

use RuntimeException;

class ErrorTransactionException extends RuntimeException
{
//
}
88 changes: 71 additions & 17 deletions src/Model.php
Expand Up @@ -15,10 +15,12 @@
use Illuminate\Database\Eloquent\Concerns\HidesAttributes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Redis as RedisFacade;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use JsonSerializable;
use Redis;
use ReflectionClass;

abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, CanBeEscapedWhenCastToString
{
Expand Down Expand Up @@ -68,7 +70,14 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*
* @var string
*/
protected $primaryKey = null;
protected $primaryKey = 'id';

/**
* The "type" of the primary key ID.
*
* @var string
*/
protected $keyType = 'string';

/**
* The model's sub keys for the model.
Expand Down Expand Up @@ -103,7 +112,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*
* @var bool
*/
public $incrementing = false;
public $incrementing = true;

/**
* Allow undeclared fillable to appear in the model.
Expand Down Expand Up @@ -339,7 +348,7 @@ public function getRedisPrefix()
public function setConnection(string $connectionName)
{
try {
$this->connection = Redis::connection($connectionName);
$this->connection = RedisFacade::connection($connectionName);
$this->setPrefixConnector();
} catch (Exception $e) {
throw new ErrorConnectToRedisException($e->getMessage());
Expand All @@ -348,6 +357,26 @@ public function setConnection(string $connectionName)
return $this;
}

/**
* Join a Redis transaction with the current connection.
*
* @param Redis $connection
*
* @return $this
*/
public function joinTransaction(Redis $clientTransaction)
{
tap($this->connection, function ($connect) use ($clientTransaction) {
$reflectionClass = new ReflectionClass(\get_class($connect));
$client = $reflectionClass->getProperty('client');
$client->setAccessible(true);
$client->setValue($connect, $clientTransaction);
$this->connection = $connect;
});

return $this;
}

/**
* Get connection
*
Expand Down Expand Up @@ -517,6 +546,10 @@ protected function performInsert(Builder $build)
$this->updateTimestamps();
}

if ($this->getIncrementing() && $this->getKeyType() && $this->getKey() == null) {
$this->setKey(Str::uuid());
}

if (!$this->isPrioritizeForceSave()) {
if ($this->getPreventCreateForce() && $this->isKeyExist()) {
throw new KeyExistException(
Expand All @@ -525,10 +558,6 @@ protected function performInsert(Builder $build)
}
}

// if ($this->usesUniqueIds()) {
// $this->setUniqueIds();
// }

// If the model has an incrementing key, we can use the "insertGetId" method on
// the query builder, which will give us back the final inserted ID for this
// table from the database. Not all tables have to be incrementing though.
Expand All @@ -538,9 +567,6 @@ protected function performInsert(Builder $build)
return true;
}

// if ($this->getIncrementing()) {
// $this->insertAndSetId($attributes);
// }
if ($this->isValidationKeyAndSubKeys($attributes)) {
$attributes = collect($attributes)->map(function ($item, $key) {
// Cast the attribute if necessary
Expand Down Expand Up @@ -569,6 +595,12 @@ protected function performInsert(Builder $build)
return true;
}

/**
* Deletes the current model from Redis if the primary key and sub-keys are valid.
* If the delete operation is successful, it returns true. Otherwise, it returns false.
*
* @return bool Returns true if the deletion is successful; otherwise, false.
*/
public function performDeleteOnModel()
{
if ($this->isValidationKeyAndSubKeys($this->getOriginal())) {
Expand All @@ -588,16 +620,25 @@ public function performDeleteOnModel()
* Inserts multiple data into Redis hashes.
*
* @param array $dataInsert An array of data to insert into Redis hashes.
* @param Redis $hasTransaction a redis client.
*
* @return mixed Returns the result of inserting multiple Redis hashes, or false if the data is invalid.
*/
public static function insert(array $dataInsert)
public static function insert(array $dataInsert, Redis $hasTransaction = null)
{
$inserts = [];
$build = static::query();
$model = $build->getModel();

if ($hasTransaction) {
$model->joinTransaction($hasTransaction);
}

foreach ($dataInsert as $attributes) {
if ($model->getIncrementing() && $model->getKeyType() && !isset($attributes[$model->getKeyName()])) {
$attributes[$model->getKeyName()] = Str::uuid();
}

if ($model->isValidationKeyAndSubKeys($attributes)) {
$key = $build->compileHashByFields($attributes);

Expand Down Expand Up @@ -748,6 +789,20 @@ public static function query()
return (new static )->newQuery();
}

/**
* Run a transaction with the given callback.
*
* @param callable $callback
*
* @return mixed The result of the callback
*/
public static function transaction(callable $callback)
{
$build = static::query();

return $build->getRepository()->transaction($callback);
}

/**
* Get a new query builder for the model's table.
*
Expand All @@ -774,9 +829,10 @@ public function newBuilder($connection)
*
* @param array $attributes
* @param bool $exists
* @param string $redisKey
* @return static
*/
public function newInstance($attributes = [], $exists = false, $redisKey = null)
public function newInstance($attributes = [], $exists = false, string $redisKey = null)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
Expand All @@ -789,8 +845,6 @@ public function newInstance($attributes = [], $exists = false, $redisKey = null)

$this->setDateFormat("Y-m-d\\TH:i:sP");

$model->setConnection($this->getConnectionName());

$model->setTable($this->getTable());

$model->mergeCasts($this->casts);
Expand Down Expand Up @@ -966,7 +1020,7 @@ public function __wakeup()
{
}

/**
/**
* Determine if the given attribute exists.
*
* @param mixed $offset
Expand All @@ -975,7 +1029,7 @@ public function __wakeup()
public function offsetExists($offset): bool
{
try {
return ! is_null($this->getAttribute($offset));
return null === $this->getAttribute($offset);
} catch (MissingAttributeException) {
return false;
}
Expand Down

0 comments on commit 02abdf7

Please sign in to comment.