Skip to content

Commit 034c4e6

Browse files
authored
Merge pull request GoogleCloudPlatform#1053 from jdpedrie/cloudsql/sqlserver
feat: add sqlserver sample
2 parents 72c35fd + 806d553 commit 034c4e6

File tree

12 files changed

+720
-2
lines changed

12 files changed

+720
-2
lines changed

cloud_sql/postgres/pdo/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,3 @@ Next, the following command will deploy the application to your Google Cloud pro
5050
```bash
5151
$ gcloud app deploy
5252
```
53-

cloud_sql/postgres/pdo/app.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ env_variables:
2626
# serve a custom PHP front controller (e.g. "serve backend/index.php") or to
2727
# run a long-running PHP script as a worker process (e.g. "php worker.php").
2828
#
29-
# entrypoint: serve index.php
29+
# entrypoint: serve index.php

cloud_sql/sqlserver/pdo/Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM gcr.io/google_appengine/php72
2+
3+
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
4+
5+
COPY . .
6+
7+
RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \
8+
curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
9+
10+
RUN apt-get update && \
11+
ACCEPT_EULA=Y apt-get -y install \
12+
autoconf \
13+
build-essential \
14+
msodbcsql17 \
15+
unixodbc-dev \
16+
unzip
17+
18+
RUN pecl install pdo_sqlsrv
19+
RUN echo "extension=pdo_sqlsrv.so" > /opt/php72/lib/ext.enabled/ext-pdo_sqlsrv.ini
20+
# RUN phpenmod pdo_sqlsrv
21+
RUN composer update

cloud_sql/sqlserver/pdo/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Connection to Cloud SQL - SQL Server
2+
3+
## Before you begin
4+
5+
1. This code sample requires the `pdo_sqlsrv` extension to be installed and enabled. For more information, including getting started guides, refer to the [source repository](https://github.com/Microsoft/msphpsql).
6+
2. Before you use this code sample, you need to have [Composer](https://getcomposer.org/) installed or downloaded into this folder. Download instructions can be found [here](https://getcomposer.org/download/). Once you've installed composer, use it to install required dependencies by running `composer install`.
7+
3. Create a SQL Server Cloud SQL Instance by following these [instructions](https://cloud.google.com/sql/docs/sqlserver/create-instance). Note the connection string, database user, and database password that you create.
8+
4. Create a database for your application by following these [instructions](https://cloud.google.com/sql/docs/sqlserver/create-manage-databases). Note the database name.
9+
5. Create a service account with the 'Cloud SQL Client' permissions by following these [instructions](https://cloud.google.com/sql/docs/postgres/connect-external-app#4_if_required_by_your_authentication_method_create_a_service_account). Download a JSON key to use to authenticate your connection.
10+
11+
## Running Locally
12+
13+
To run this application locally, download and install the `cloud_sql_proxy` by following the instructions [here](https://cloud.google.com/sql/docs/sqlserver/sql-proxy#install).
14+
15+
To authenticate with Cloud SQL, set the `$GOOGLE_APPLICATION_CREDENTIALS` environment variable:
16+
17+
```bash
18+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service/account/key.json
19+
```
20+
21+
To run the Cloud SQL proxy, you need to set the instance connection name. See the instructions [here](https://cloud.google.com/sql/docs/sqlserver/quickstart-proxy-test#get_the_instance_connection_name) for finding the instance connection name.
22+
23+
```bash
24+
export CLOUD_SQL_CONNECTION_NAME='::'
25+
```
26+
27+
Once the proxy is ready, use one of the following commands to start the proxy in the background.
28+
29+
You may connect to your instance via TCP. To connect via TCP, you must provide a port as part of the instance name, as demonstrated below.
30+
31+
```bash
32+
$ ./cloud_sql_proxy \
33+
--instances=$CLOUD_SQL_CONNECTION_NAME=tcp:1433 \
34+
--credential_file=$GOOGLE_APPLICATION_CREDENTIALS
35+
```
36+
37+
### Set Configuration Values
38+
39+
Set the required environment variables for your connection to Cloud SQL.
40+
41+
```bash
42+
export DB_USER='my-db-user'
43+
export DB_PASS='my-db-pass'
44+
export DB_NAME='my-db-name'
45+
export DB_HOSTNAME='127.0.0.1'
46+
```
47+
48+
Note: Saving credentials in environment variables is convenient, but not secure - consider a more secure solution such as [Secret Manager](https://cloud.google.com/secret-manager/) to help keep secrets safe.
49+
50+
Execute the following:
51+
52+
```bash
53+
$ php -S localhost:8080
54+
```
55+
56+
Navigate towards http://localhost:8080 to verify your application is running correctly.
57+
58+
## Google App Engine Flex
59+
60+
To run on App Engine Flex, create an App Engine project by following the setup for these [instructions](https://cloud.google.com/appengine/docs/standard/php7/quickstart#before-you-begin).
61+
62+
First, update `app.yaml` with the correct values to pass the environment variables into the runtime.
63+
64+
In order to use the `sqlsrv` extension, you will need to build a [custom runtime](https://cloud.google.com/appengine/docs/flexible/custom-runtimes/quickstart). The `Dockerfile` in this sample contains a simple example of a custom PHP 7.2 runtime based off of the default App Engine Flex image with the `pdo_sqlsrv` extension installed.
65+
66+
Then, make sure that the service account `service-{PROJECT_NUMBER}>@gae-api-prod.google.com.iam.gserviceaccount.com` has the IAM role `Cloud SQL Client`.
67+
68+
Next, the following command will deploy the application to your Google Cloud project:
69+
70+
```bash
71+
$ gcloud beta app deploy
72+
```

cloud_sql/sqlserver/pdo/app.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
runtime: custom
16+
env: flex
17+
18+
# Remember - storing secrets in plaintext is potentially unsafe. Consider using
19+
# something like https://cloud.google.com/secret-manager/ to help keep secrets
20+
# secret.
21+
env_variables:
22+
DB_USER: my-db-user
23+
DB_PASS: my-db-pass
24+
DB_NAME: my-db
25+
DB_HOSTNAME: "172.17.0.1"
26+
27+
beta_settings:
28+
# The connection name of your instance, available by using
29+
# 'gcloud beta sql instances describe [INSTANCE_NAME]' or from
30+
# the Instance details page in the Google Cloud Platform Console.
31+
cloud_sql_instances: ::=tcp:1433
32+
33+
# Defaults to "serve index.php" and "serve public/index.php". Can be used to
34+
# serve a custom PHP front controller (e.g. "serve backend/index.php") or to
35+
# run a long-running PHP script as a worker process (e.g. "php worker.php").
36+
#
37+
# entrypoint: serve index.php

cloud_sql/sqlserver/pdo/composer.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "google/cloud-sql-sqlserver-example",
3+
"autoload": {
4+
"psr-4": {
5+
"Google\\Cloud\\Samples\\CloudSQL\\SQLServer\\": "src"
6+
}
7+
},
8+
"autoload-dev": {
9+
"psr-4": {
10+
"Google\\Cloud\\Samples\\CloudSQL\\SQLServer\\Tests\\": "src"
11+
}
12+
},
13+
"require": {
14+
"php": ">= 7.2",
15+
"ext-pdo_sqlsrv": "*",
16+
"slim/slim": "^4.5",
17+
"slim/twig-view": "^3.1",
18+
"pimple/pimple": "^3.3",
19+
"guzzlehttp/psr7": "^1.6",
20+
"http-interop/http-factory-guzzle": "^1.0"
21+
},
22+
"require-dev": {
23+
"phpunit/phpunit": "^8.5"
24+
}
25+
}

cloud_sql/sqlserver/pdo/index.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
/*
3+
* Copyright 2020 Google LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
use GuzzleHttp\Psr7;
21+
22+
include __DIR__ . '/vendor/autoload.php';
23+
24+
$app = include __DIR__ . '/src/app.php';
25+
26+
$app->get('/', function ($request, $response) {
27+
$this->get('votes')->createTableIfNotExists();
28+
29+
return $this->get('view')->render($response, 'template.twig', [
30+
'votes' => $this->get('votes')->listVotes(),
31+
'tabCount' => $this->get('votes')->getCountByValue('TABS'),
32+
'spaceCount' => $this->get('votes')->getCountByValue('SPACES'),
33+
]);
34+
});
35+
36+
$app->post('/', function ($request, $response) {
37+
$this->get('votes')->createTableIfNotExists();
38+
39+
$message = 'Invalid vote. Choose Between TABS and SPACES';
40+
41+
$formData = $request->getParsedBody() + [
42+
'voteValue' => ''
43+
];
44+
45+
if (in_array($formData['voteValue'], ['SPACES', 'TABS'])) {
46+
$message = $this->get('votes')->insertVote($formData['voteValue'])
47+
? 'Vote cast for ' . $formData['voteValue']
48+
: 'An error occurred';
49+
}
50+
51+
return $response->withBody(Psr7\stream_for($message));
52+
});
53+
54+
$app->run();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
xml version="1.0" encoding="UTF-8"?>
2+
<phpunit colors="true">
3+
<testsuites>
4+
<testsuite name="CloudSQLSQLServerSample">
5+
<directory>testsdirectory>
6+
testsuite>
7+
testsuites>
8+
<filter>
9+
<whitelist>
10+
<directory suffix=".php">srcdirectory>
11+
whitelist>
12+
filter>
13+
phpunit>

cloud_sql/sqlserver/pdo/src/Votes.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
2+
/*
3+
* Copyright 2020 Google LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace Google\Cloud\Samples\CloudSQL\SQLServer;
21+
22+
use PDO;
23+
use PDOException;
24+
use RuntimeException;
25+
26+
/**
27+
* Manage votes using the Cloud SQL database.
28+
*/
29+
class Votes
30+
{
31+
/**
32+
* @var PDO
33+
*/
34+
private $connection;
35+
36+
/**
37+
* @param PDO $connection A connection to the database.
38+
*/
39+
public function __construct(PDO $connection)
40+
{
41+
$this->connection = $connection;
42+
}
43+
44+
/**
45+
* Creates the table if it does not yet exist.
46+
*
47+
* @return void
48+
*/
49+
public function createTableIfNotExists()
50+
{
51+
$existsStmt = "SELECT * FROM INFORMATION_SCHEMA.TABLES
52+
WHERE TABLE_NAME = ?";
53+
54+
$stmt = $this->connection->prepare($existsStmt);
55+
$stmt->execute(['votes']);
56+
57+
$row = $stmt->fetch(PDO::FETCH_ASSOC);
58+
59+
// If the table does not exist, create it.
60+
if (!$row) {
61+
$sql = "CREATE TABLE votes (
62+
vote_id INT NOT NULL IDENTITY,
63+
time_cast DATETIME NOT NULL,
64+
vote_value VARCHAR(6) NOT NULL,
65+
PRIMARY KEY (vote_id)
66+
);";
67+
68+
$this->connection->exec($sql);
69+
}
70+
}
71+
72+
/**
73+
* Returns a list of the last five votes
74+
*
75+
* @return array
76+
*/
77+
public function listVotes() : array
78+
{
79+
$sql = "SELECT TOP 5 vote_value, time_cast FROM votes ORDER BY time_cast DESC";
80+
$statement = $this->connection->prepare($sql);
81+
$statement->execute();
82+
return $statement->fetchAll(PDO::FETCH_ASSOC);
83+
}
84+
85+
/**
86+
* Get the number of votes cast for a given value.
87+
*
88+
* @param string $value
89+
* @param int
90+
*/
91+
public function getCountByValue(string $value) : int
92+
{
93+
$sql = "SELECT COUNT(vote_id) as voteCount FROM votes WHERE vote_value = ?";
94+
95+
$statement = $this->connection->prepare($sql);
96+
$statement->execute([$value]);
97+
98+
return (int) $statement->fetch(PDO::FETCH_COLUMN);
99+
}
100+
101+
/**
102+
* Insert a new vote into the database
103+
*
104+
* @param string $value The value to vote for.
105+
* @return boolean
106+
*/
107+
public function insertVote(string $value) : bool
108+
{
109+
$conn = $this->connection;
110+
$res = false;
111+
112+
# [START cloud_sql_sqlserver_pdo_connection]
113+
// Use prepared statements to guard against SQL injection.
114+
$sql = "INSERT INTO votes (time_cast, vote_value) VALUES (GETDATE(), :voteValue)";
115+
116+
try {
117+
$statement = $conn->prepare($sql);
118+
$statement->bindParam('voteValue', $value);
119+
120+
$res = $statement->execute();
121+
} catch (PDOException $e) {
122+
throw new RuntimeException(
123+
"Could not insert vote into database. The PDO exception was " .
124+
$e->getMessage(),
125+
$e->getCode(),
126+
$e
127+
);
128+
}
129+
# [END cloud_sql_sqlserver_pdo_connection]
130+
131+
return $res;
132+
}
133+
}

0 commit comments

Comments
 (0)