{"id":22576113,"url":"https://github.com/colopl/laravel-spanner","last_synced_at":"2025-05-16T04:06:06.685Z","repository":{"id":34211622,"uuid":"171383628","full_name":"colopl/laravel-spanner","owner":"colopl","description":"Laravel database driver for Google Cloud Spanner","archived":false,"fork":false,"pushed_at":"2025-05-12T09:25:52.000Z","size":571,"stargazers_count":102,"open_issues_count":11,"forks_count":16,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-05-13T14:16:51.788Z","etag":null,"topics":["google-cloud-platform","google-cloud-spanner","laravel","php"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/colopl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-19T01:33:26.000Z","updated_at":"2025-05-02T08:45:55.000Z","dependencies_parsed_at":"2024-04-16T10:41:16.457Z","dependency_job_id":"d08fd65e-d185-4279-ae70-e705c90b8c7e","html_url":"https://github.com/colopl/laravel-spanner","commit_stats":{"total_commits":174,"total_committers":9,"mean_commits":"19.333333333333332","dds":"0.43103448275862066","last_synced_commit":"187f6cdacb6482d8a7b1141e3e1c3df16a02e86a"},"previous_names":[],"tags_count":66,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colopl%2Flaravel-spanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colopl%2Flaravel-spanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colopl%2Flaravel-spanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colopl%2Flaravel-spanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/colopl","download_url":"https://codeload.github.com/colopl/laravel-spanner/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254464895,"owners_count":22075570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["google-cloud-platform","google-cloud-spanner","laravel","php"],"created_at":"2024-12-08T04:06:06.904Z","updated_at":"2025-05-16T04:06:06.661Z","avatar_url":"https://github.com/colopl.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"laravel-spanner\n================\n\nLaravel database driver for Google Cloud Spanner\n\n[![License](https://img.shields.io/packagist/l/colopl/laravel-spanner.svg?style=flat-square)](https://github.com/colopl/laravel-spanner/blob/master/LICENSE)\n[![Latest Stable Version](https://img.shields.io/packagist/v/colopl/laravel-spanner.svg?style=flat-square)](https://packagist.org/packages/colopl/laravel-spanner)\n[![Minimum PHP Version](https://img.shields.io/packagist/php-v/colopl/laravel-spanner.svg?style=flat-square)](https://secure.php.net/)\n\n## Requirements\n\n- PHP \u003e= 8.2\n- Laravel \u003e= 11\n- [gRPC extension](https://cloud.google.com/php/grpc)\n- [protobuf extension](https://cloud.google.com/php/grpc#install_the_protobuf_runtime_library) (recommended for better performance)\n- `sysvmsg`, `sysvsem`, `sysvshm` extensions (recommended for better performance)\n\n## Installation\nPut JSON credential file path to env variable: `GOOGLE_APPLICATION_CREDENTIALS`\n\n```\nexport GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json\n```\n\nInstall via composer\n\n```sh\ncomposer require colopl/laravel-spanner\n```\n\nAdd connection config to `config/database.php`\n\n```php\n[\n    'connections' =\u003e [\n        'spanner' =\u003e [\n            'driver' =\u003e 'spanner',\n            'instance' =\u003e '\u003cCloud Spanner instanceId here\u003e',\n            'database' =\u003e '\u003cCloud Spanner database name here\u003e',\n        ]\n    ]\n];\n```\n\nThat's all. You can use database connection as usual.\n\n```php\n$conn = DB::connection('spanner');\n$conn-\u003e...\n```\n\n## Additional Configurations\nYou can pass `SpannerClient` config and `CacheSessionPool` options as below.\nFor more information, please see [Google Client Library docs](http://googleapis.github.io/google-cloud-php/#/docs/google-cloud/latest/spanner/spannerclient?method=__construct)\n\n```php\n[\n    'connections' =\u003e [\n        'spanner' =\u003e [\n            'driver' =\u003e 'spanner',\n            'instance' =\u003e '\u003cCloud Spanner instanceId here\u003e',\n            'database' =\u003e '\u003cCloud Spanner database name here\u003e',\n            \n            // Spanner Client configurations\n            'client' =\u003e [\n                'projectId' =\u003e 'xxx',\n                ...\n            ],\n            \n            // CacheSessionPool options\n            'session_pool' =\u003e [\n                'minSessions' =\u003e 10,\n                'maxSessions' =\u003e 500,\n            ],\n        ]\n    ]\n];\n```\n\n## Recommended Setup\n\nPlease note that the following are not required, but are strongly recommended for better performance.\n\n- Install `protobuf` pecl extension for faster network communication.\n- Install `sysvmsg`, `sysvsem`, `sysvshm` extensions for faster session management.\n- Mount the cache directory (`./storage/framework/spanner` by default) to tmpfs for better session io performance. \n  Cache path can be changed by setting `connections.{name}.cache_path` in your `config/database.php` file.\n\n## Unsupported features\n\n- STRUCT data types\n- Inserting/Updating JSON data types\n\n## Limitations\n\n### SQL Mode\nCurrently only supports Spanner running GoogleSQL (PostgreSQL mode is not supported).\n\n### Query\n- [Binding more than 950 parameters in a single query will result in an error](https://cloud.google.com/spanner/quotas#query-limits)\n  by the server. In order to by-pass this limitation, this driver will attempt to switch to using `Query\\Builder::whereInUnnest(...)`\n  internally when the passed parameter exceeds the limit set by `parameter_unnest_threshold` config (default: `900`).\n  You can turn this feature off by setting the value to `false`.\n\n### Eloquent\nIf you use interleaved keys, you MUST define them in the `interleaveKeys` property, or else you won't be able to save. \nFor more detailed instructions, see `Colopl\\Spanner\\Tests\\Eloquent\\ModelTest`.\n\n## Additional Information\n\n### Migrations\n\nSince Cloud Spanner does not support AUTO_INCREMENT attribute, `Blueprint::increments` (and all of its variants) will \ncreate a column of type `STRING(36) DEFAULT (GENERATE_UUID())` to generate and fill the column with a UUID\nand flag it as a primary key.\n\n### Transactions\nGoogle Cloud Spanner sometimes requests transaction retries (e.g. `UNAVAILABLE`, and `ABORTED`), even if the logic is correct. For that reason, please do not manage transactions manually.\n\nYou should always use the `transaction` method which handles retry requests internally.\n\n```php\n// BAD: Do not use transactions manually!!\ntry {\n    DB::beginTransaction();\n    ...\n    DB::commit();\n} catch (\\Throwable $ex) {\n    DB::rollBack();\n}\n\n// GOOD: You should always use transaction method\nDB::transaction(function() {\n    ...\n});\n```\n\nGoogle Cloud Spanner creates transactions for all data operations even if you do not explicitly create transactions.\n\nIn particular, in the SELECT statement, the type of transaction varies depending on whether it is explicit or implicit.\n\n```php\n// implicit transaction (Read-only transaction)\n$conn-\u003eselect('SELECT ...');\n\n// explicit transaction (Read-write transaction)\n$conn-\u003etransaction(function() {\n    $conn-\u003eselect('SELECT ...');\n});\n\n// implicit transaction (Read-write transaction)\n$conn-\u003einsert('INSERT ...');\n\n// explicit transaction (Read-write transaction)\n$conn-\u003etransaction(function() {\n    $conn-\u003einsert('INSERT ...');\n});\n```\n\n| Transaction type | **SELECT** statement | **INSERT/UPDATE/DELETE** statement |\n| :--- | :--- | :--- |\n| implicit transaction | **Read-only** transaction with **singleUse** option | **Read-write** transaction with **singleUse** option |\n| explicit transaction | **Read-write** transaction | **Read-write** transaction |\n\nFor more information, see [Cloud Spanner Documentation about transactions](https://cloud.google.com/spanner/docs/transactions)\n\n### Stale reads\n\nYou can use [Stale reads (timestamp bounds)](https://cloud.google.com/spanner/docs/timestamp-bounds) as below.\n\n```php\n// There are four types of timestamp bounds: ExactStaleness, MaxStaleness, MinReadTimestamp and ReadTimestamp.\n$timestampBound = new ExactStaleness(10);\n\n// by Connection\n$connection-\u003eselectWithTimestampBound('SELECT ...', $bindings, $timestampBound);\n\n// by Query Builder\n$queryBuilder\n    -\u003ewithStaleness($timestampBound)\n    -\u003eget();\n```\n\nStale reads always runs as read-only transaction with `singleUse` option. So you can not run as read-write transaction.\n\n### Snapshot reads\n\nYou can use explicit Snapshot reads, either on `Connection`, or on `Model` or `Builder` instances. When running `snapshot()` on `Connection`, you pass a `Closure` that you can use to run multiple reads from within the same Snapshot.\n\n```php\n$timestampBound = new ExactStaleness(10);\n\n// by Connection\n$connection-\u003esnapshot($timestampBound, function() use ($connection) {\n    $result1 = $connection-\u003etable('foo')-\u003eget();\n    $result2 = $connection-\u003etable('bar')-\u003eget();\n\n    return [$result1, $result2];\n);\n\n// by Model\nUser::where('foo', 'bar')\n    -\u003esnapshot($timestampBound)\n    -\u003eget();\n\n// by Query Builder\n$queryBuilder\n    -\u003esnapshot($timestampBound)\n    -\u003eget();\n```\n\n### Data Boost\n\nData boost creates snapshot and runs the query in parallel without affecting existing workloads.\n\nYou can read more about it [here](https://cloud.google.com/spanner/docs/databoost/databoost-overview).\n\nBelow are some examples of how to use it.\n\n```php\n// Using Connection\n$connection-\u003eselectWithOptions('SELECT ...', $bindings, ['dataBoostEnabled' =\u003e true]);\n\n// Using Query Builder\n$queryBuilder\n    -\u003euseDataBoost()\n    -\u003esetRequestTimeoutSeconds(60)\n    -\u003eget();\n```\n\n\u003e [!NOTE]\n\u003e This creates a new session in the background which is not shared with the current session pool.\n\u003e This means, queries running with data boost will not be associated with transactions that may be taking place.\n\n### Request Tags and Transaction Tags\n\nSpanner allows you to attach tags to your queries and transactions that can be [used for troubleshooting](https://cloud.google.com/spanner/docs/introspection/troubleshooting-with-tags).\n\nYou can set request tags and transaction tags as below.\n\n```php\n$requestPath = request()-\u003epath();\n$tag = 'url=' . $requestPath;\n$connection-\u003esetRequestTag($tag);\n$connection-\u003esetTransactionTag($tag);\n```\n\n### Data Types\n\nSome data types of Google Cloud Spanner does not have corresponding built-in type of PHP.\nYou can use following classes by [Google Cloud PHP Client](https://github.com/googleapis/google-cloud-php)\n\n- BYTES: `Google\\Cloud\\Spanner\\Bytes`\n- DATE: `Google\\Cloud\\Spanner\\Date`\n- NUMERIC: `Google\\Cloud\\Spanner\\Numeric`\n- TIMESTAMP: `Google\\Cloud\\Spanner\\Timestamp`\n\nWhen fetching rows, the library coverts the following column types\n- `Timestamp` -\u003e [Carbon](https://laravel.com/api/10.x/Illuminate/Support/Carbon.html) with the default timezone in PHP\n- `Numeric` -\u003e `string`\n\nNote that if you execute a query without QueryBuilder, it will not have these conversions.\n\n\n### Partitioned DML\nYou can run partitioned DML as below.\n\n```php\n// by Connection\n$connection-\u003erunPartitionedDml('UPDATE ...');\n\n\n// by Query Builder\n$queryBuilder-\u003epartitionedUpdate($values);\n$queryBuilder-\u003epartitionedDelete();\n```\n\nHowever, Partitioned DML has some limitations. See [Cloud Spanner Documentation about Partitioned DML](https://cloud.google.com/spanner/docs/dml-partitioned#dml_and_partitioned_dml) for more information.\n\n\n### Interleave\nYou can define [interleaved tables](https://cloud.google.com/spanner/docs/schema-and-data-model#creating_a_hierarchy_of_interleaved_tables) as below.\n\n```php\n$schemaBuilder-\u003ecreate('user_items', function (Blueprint $table) {\n    $table-\u003euuid('user_id');\n    $table-\u003euuid('id');\n    $table-\u003euuid('item_id');\n    $table-\u003einteger('count');\n    $table-\u003etimestamps();\n\n    $table-\u003eprimary(['user_id', 'id']);\n    \n    // interleaved table\n    $table-\u003einterleaveInParent('users')-\u003ecascadeOnDelete();\n    \n    // interleaved index\n    $table-\u003eindex(['userId', 'created_at'])-\u003einterleaveIn('users');\n});\n```\n\n### Row Deletion Policy\n\nYou can define [row deletion policy](https://cloud.google.com/spanner/docs/ttl/working-with-ttl) as below.\n\n```php\n$schemaBuilder-\u003ecreate('user', function (Blueprint $table) {\n    $table-\u003euuid('user_id');\n    $table-\u003etimestamps();\n    \n    // create a policy\n    $table-\u003edeleteRowsOlderThan(['updated_at'], 365);\n});\n\n$schemaBuilder-\u003etable('user', function (Blueprint $table) {\n    // add policy\n    $table-\u003eaddRowDeletionPolicy('udpated_at', 100);\n\n    // replace policy\n    $table-\u003ereplaceRowDeletionPolicy('udpated_at', 100);\n\n    // drop policy\n    $table-\u003edropRowDeletionPolicy();\n});\n```\n\n### Sequence\n\nIf you want a simple sequence to be used as a primary key, you can use `useSequence()` method.\nIf `useSequence()` is called without providing a `$name`, a sequence with name `user_id_sequence` will be created\nwith `start_with_counter` set with a random value between 1 and 1,000,000.\n\n```php\n$schemaBuilder-\u003ecreate('user', function (Blueprint $table) {\n    $table-\u003einteger('id')-\u003euseSequence();\n});\n```\n\nIf you want more flexibility, you can also create, alter, and drop sequences directly as below.\n\n```php\n$schemaBuilder-\u003ecreate('user_items', function (Blueprint $table) {\n    $table-\u003ecreateSequence('sequence_name');\n    $table-\u003einteger('id')-\u003euseSequence('sequence_name');\n    \n    $table-\u003ealterSequence('sequence_name')\n        -\u003estartWithCounter(100)\n        -\u003eskipRangeMin(1)\n        -\u003eskipRangeMax(10);\n    \n    $table-\u003edropSequence('sequence_name');\n});\n```\n\n### Change Streams\n\nSpanner supports [Change Streams](https://cloud.google.com/spanner/docs/change-streams) which allows you to listen to changes in the database.\nChange streams can be created/altered/dropped through the schema builder as shown below.\n\n```php\n$schemaBuilder-\u003ecreate('user_items', function (Blueprint $table) {\n    $table-\u003ecreateChangeStream('stream_name')\n        -\u003efor('user_items', ['userId', 'userItemId'])\n        -\u003eretentionPeriod('7d')\n        -\u003evalueCaptureType(ChangeStreamValueCaptureType::NewValues)\n        -\u003eexcludeTtlDeletes(true);\n\n    $table-\u003ecreateChangeStream('stream_name')\n        -\u003eexcludeInsert(true)\n        -\u003eexcludeUpdate(true)\n        -\u003eexcludeDelete(true);\n    \n    $table-\u003edropChangeStream('stream_name');\n});\n```\n\n### Full Text Search\n\nSpanner supports [Full Text Search](https://cloud.google.com/spanner/docs/full-text-search) which allows you to search for text in columns.\n\nYou can define a token list column and a search index for the column as below.\n\n```php\n$schemaBuilder-\u003ecreate('user', function (Blueprint $table) {\n    $table-\u003euuid('id')-\u003eprimary();\n    $table-\u003estring('name');\n    // adds an invisible column for full text search\n    $table-\u003etokenList('UserNameTokens', TokenizerFunction::FullText, 'name', ['language_tag' =\u003e 'en']);\n    \n    // adds a SEARCH INDEX\n    $table-\u003efullText(['UserNameTokens']);\n});\n```\n\nOnce the schema has been applied, you can use the search methods in the query builder to search for text in the columns as below.\n\n```php\nUser::query()-\u003esearchFullText('UserNameTokens', 'John OR Kevin', ['enhance_query' =\u003e true])-\u003eget();\n```\n\nThe methods available are `searchFullText`, `searchSubstring`, and `searchNgrams`.\n\n### Secondary Index Options\n\nYou can define Spanner specific index options like [null filtering](https://cloud.google.com/spanner/docs/secondary-indexes#null-indexing-disable) and [storing](https://cloud.google.com/spanner/docs/secondary-indexes#storing-clause) as below.\n\n```php\n$schemaBuilder-\u003etable('user_items', function (Blueprint $table) {\n    $table-\u003eindex('userId')\n        // Interleave in parent table\n        -\u003einterleaveIn('user')\n        // Add null filtering\n        -\u003enullFiltered()\n        // Add storing\n        -\u003estoring(['itemId', 'count']);\n});\n```\n\n### Mutations\n\nYou can [insert, update, and delete data using mutations](https://cloud.google.com/spanner/docs/modify-mutation-api) to modify data instead of using DML to improve performance.\n\n```\n$queryBuilder-\u003einsertUsingMutation($values);\n$queryBuilder-\u003eupdateUsingMutation($values);\n$queryBuilder-\u003einsertOrUpdateUsingMutation($values);\n$queryBuilder-\u003edeleteUsingMutation($values);\n```\n\nPlease note that mutation api does not work the same way as DML.\nAll mutations calls within a transaction are queued and sent as batch at the time you commit.\nThis means that if you make any modifications through the above functions and then try to SELECT the same records before committing, the returned results will not include any of the modifications you've made inside the transaction.\n\n\n### SessionPool and AuthCache\n\nIn order to improve the performance of the first connection per request, we use [AuthCache](https://github.com/googleapis/google-cloud-php#caching-access-tokens) and [CacheSessionPool](https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/latest/spanner/session/cachesessionpool).\n\nBy default, this library uses [Filesystem Cache Adapter](https://symfony.com/doc/current/components/cache/adapters/filesystem_adapter.html) as the caching pool. If you want to use your own caching pool, you can extend ServiceProvider and inject it into the constructor of `Colopl\\Spanner\\Connection`.\n\nThe initialization of each session takes about a second, so warming up the sessions during the boot up phase of your\nserver is recommended. This can be achieved by running the `php artisan spanner:warmup` command. You can set the number\nof sessions to warm up by setting the `connections.{name}.session_pool.maxSessions` option in `config/database.php`\n\nSimilarly, the sessions remain active for 60 minutes after use so deleting the sessions during the shutdown phase \nof your server is recommended. This can be achieved by running the `php artisan spanner:cooldown` command.\n\n### Queue Worker\n\nAfter every job is processed, the connection will be disconnected so the session can be released into the session pool. \nThis allows the session to be renewed (through `maintainSessionPool()`) or expire.\n\n\n### Laravel Tinker\nYou can use [Laravel Tinker](https://github.com/laravel/tinker) with commands such as `php artisan tinker`.\nBut your session may hang when accessing Cloud Spanner. This is known gRPC issue that occurs when PHP forks a process.\nThe workaround is to add following line to `php.ini`.\n\n```ini\ngrpc.enable_fork_support=1\n```\n\n\n\n## Development\n\n### Testing\nYou can run tests on docker by the following command. Note that some environment variables must be set.\nIn order to set the variables, rename [.env.sample](./.env.sample) to `.env` and edit the values of the\ndefined variables.\n\n| Name | Value |\n| :-- | :--   |\n| `GOOGLE_APPLICATION_CREDENTIALS` | The path of the service account key file with access privilege to Google Cloud Spanner instance |\n| `DB_SPANNER_INSTANCE_ID` | Instance ID of your Google Cloud Spanner |\n| `DB_SPANNER_DATABASE_ID` | Name of the database with in the Google Cloud Spanner instance |\n| `DB_SPANNER_PROJECT_ID` | Not required if your credential includes the project ID  |\n\n```sh\nmake test\n```\n\n## License\nApache 2.0 - See [LICENSE](./LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolopl%2Flaravel-spanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcolopl%2Flaravel-spanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolopl%2Flaravel-spanner/lists"}