From 3fae587dc52dde75cd460447ad331f1ae454e856 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Mon, 29 Mar 2021 15:55:23 -0700
Subject: [PATCH 001/624] fix: kms test namespace
---
kms/test/kmsTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kms/test/kmsTest.php b/kms/test/kmsTest.php
index 2e03ae30ac..64ddc1887b 100644
--- a/kms/test/kmsTest.php
+++ b/kms/test/kmsTest.php
@@ -178,7 +178,7 @@ private static function waitForReady(CryptoKey $key)
while ($version->getState() != CryptoKeyVersionState::ENABLED) {
if ($attempts > 10) {
$msg = sprintf('key version %s was not ready after 10 attempts', $versionName);
- throw new Exception($msg);
+ throw new \Exception($msg);
}
usleep(500);
$version = $client->getCryptoKeyVersion($versionName);
From 3bbd25dc52a9b0a66ef3640266091dbea332ff30 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Tue, 30 Mar 2021 12:30:39 -0700
Subject: [PATCH 002/624] feat: add Cloud Compute instance samples (#1309)
---
compute/{helloworld => }/README.md | 2 +-
.../helloworld}/README.md | 3 +
.../helloworld}/app.php | 0
.../helloworld}/composer.json | 0
.../helloworld}/README.md | 0
.../helloworld}/app.php | 0
.../helloworld}/composer.json | 0
compute/cloud-client/instances/README.md | 110 ++++++++++++++++++
compute/cloud-client/instances/composer.json | 5 +
.../cloud-client/instances/phpunit.xml.dist | 34 ++++++
.../instances/src/create_instance.php | 85 ++++++++++++++
.../instances/src/delete_instance.php | 54 +++++++++
.../instances/src/list_instances.php | 51 ++++++++
.../instances/test/instancesTest.php | 70 +++++++++++
testing/sample_helpers.php | 2 +-
15 files changed, 414 insertions(+), 2 deletions(-)
rename compute/{helloworld => }/README.md (90%)
rename compute/{helloworld/api-client => api-client/helloworld}/README.md (93%)
rename compute/{helloworld/api-client => api-client/helloworld}/app.php (100%)
rename compute/{helloworld/api-client => api-client/helloworld}/composer.json (100%)
rename compute/{helloworld/cloud-client => cloud-client/helloworld}/README.md (100%)
rename compute/{helloworld/cloud-client => cloud-client/helloworld}/app.php (100%)
rename compute/{helloworld/cloud-client => cloud-client/helloworld}/composer.json (100%)
create mode 100644 compute/cloud-client/instances/README.md
create mode 100644 compute/cloud-client/instances/composer.json
create mode 100644 compute/cloud-client/instances/phpunit.xml.dist
create mode 100644 compute/cloud-client/instances/src/create_instance.php
create mode 100644 compute/cloud-client/instances/src/delete_instance.php
create mode 100644 compute/cloud-client/instances/src/list_instances.php
create mode 100644 compute/cloud-client/instances/test/instancesTest.php
diff --git a/compute/helloworld/README.md b/compute/README.md
similarity index 90%
rename from compute/helloworld/README.md
rename to compute/README.md
index 91553f7541..ddee783c19 100644
--- a/compute/helloworld/README.md
+++ b/compute/README.md
@@ -1,4 +1,4 @@
-# Google Compute Engine PHP Sample Application
+# Google Compute Engine PHP Samples
## Description
This is a simple web-based example of calling the Google Compute Engine API
diff --git a/compute/helloworld/api-client/README.md b/compute/api-client/helloworld/README.md
similarity index 93%
rename from compute/helloworld/api-client/README.md
rename to compute/api-client/helloworld/README.md
index a00972cc83..212db44e1b 100644
--- a/compute/helloworld/api-client/README.md
+++ b/compute/api-client/helloworld/README.md
@@ -1,5 +1,8 @@
# Google Compute Engine PHP Sample Application
+**NOTE: This sample is outdated. It is recommended you use the [ALPHA Compute
+Client](../../cloud-client/helloworld) instead**
+
## Description
This is a simple web-based example of calling the Google Compute Engine API
in PHP.
diff --git a/compute/helloworld/api-client/app.php b/compute/api-client/helloworld/app.php
similarity index 100%
rename from compute/helloworld/api-client/app.php
rename to compute/api-client/helloworld/app.php
diff --git a/compute/helloworld/api-client/composer.json b/compute/api-client/helloworld/composer.json
similarity index 100%
rename from compute/helloworld/api-client/composer.json
rename to compute/api-client/helloworld/composer.json
diff --git a/compute/helloworld/cloud-client/README.md b/compute/cloud-client/helloworld/README.md
similarity index 100%
rename from compute/helloworld/cloud-client/README.md
rename to compute/cloud-client/helloworld/README.md
diff --git a/compute/helloworld/cloud-client/app.php b/compute/cloud-client/helloworld/app.php
similarity index 100%
rename from compute/helloworld/cloud-client/app.php
rename to compute/cloud-client/helloworld/app.php
diff --git a/compute/helloworld/cloud-client/composer.json b/compute/cloud-client/helloworld/composer.json
similarity index 100%
rename from compute/helloworld/cloud-client/composer.json
rename to compute/cloud-client/helloworld/composer.json
diff --git a/compute/cloud-client/instances/README.md b/compute/cloud-client/instances/README.md
new file mode 100644
index 0000000000..169e7ed327
--- /dev/null
+++ b/compute/cloud-client/instances/README.md
@@ -0,0 +1,110 @@
+Google Cloud Compute PHP Instances Samples
+==========================================
+
+[![Open in Cloud Shell][shell_img]][shell_link]
+
+[shell_img]: http://gstatic.com/cloudssh/images/open-btn.svg
+[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googlecloudplatform/php-docs-samples&page=editor&working_dir=compute/cloud-client/instances
+
+This directory contains samples for calling [Google Cloud Compute][compute]
+from PHP. Specifically, they show how to manage your Compute Engine [instances][instances].
+
+[compute]: https://cloud.google.com/compute/docs/apis
+[instances]: https://cloud.google.com/compute/docs/instances/stop-start-instance
+
+## Setup
+
+### Authentication
+
+Authentication is typically done through [Application Default Credentials][adc]
+which means you do not have to change the code to authenticate as long as
+your environment has credentials. You have a few options for setting up
+authentication:
+
+1. When running locally, use the [Google Cloud SDK][google-cloud-sdk]
+
+ gcloud auth application-default login
+
+1. When running on App Engine or Compute Engine, credentials are already
+ set-up. However, you may need to configure your Compute Engine instance
+ with [additional scopes][additional_scopes].
+
+1. You can create a [Service Account key file][service_account_key_file]. This file can be used to
+ authenticate to Google Cloud Platform services from any environment. To use
+ the file, set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to
+ the path to the key file, for example:
+
+ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account.json
+
+[adc]: https://cloud.google.com/docs/authentication#getting_credentials_for_server-centric_flow
+[additional_scopes]: https://cloud.google.com/compute/docs/authentication#using
+[service_account_key_file]: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount
+
+## Install Dependencies
+
+1. **Install dependencies** via [Composer](http://getcomposer.org/doc/00-intro.md).
+ Run `php composer.phar install` (if composer is installed locally) or `composer install`
+ (if composer is installed globally).
+
+1. Create a service account at the
+[Service account section in the Cloud Console](https://console.cloud.google.com/iam-admin/serviceaccounts/)
+
+1. Download the json key file of the service account.
+
+1. Set `GOOGLE_APPLICATION_CREDENTIALS` environment variable pointing to that file.
+
+## Samples
+
+To run the Compute Samples, run any of the files in `src/` on the CLI to print
+the usage instructions:
+
+```
+$ php src/list_instances.php
+
+Usage: list_instances.php $projectId $zone
+
+ @param string $projectId Your Google Cloud project ID.
+ @param string $zone The zone to create the instance in (e.g. "us-central1-a")
+```
+
+### Create an instance
+
+```
+$ php src/create_instance.php $YOUR_PROJECT_ID "us-central1-a" "my-new-instance-name"
+Created instance my-new-instance-name
+```
+
+### List instances
+
+```
+$ php src/list_instances.php $YOUR_PROJECT_ID "us-central1-a"
+Instances for YOUR_PROJECT_ID (us-central1-a)
+ - my-new-instance-name
+```
+
+### Delete an instance
+
+```
+$ php src/delete_instance.php $YOUR_PROJECT_ID "us-central1-a" "my-new-instance-name"
+Deleted instance my-new-instance-name
+```
+
+## Troubleshooting
+
+If you get the following error, set the environment variable `GCLOUD_PROJECT` to your project ID:
+
+```
+[Google\Cloud\Core\Exception\GoogleException]
+No project ID was provided, and we were unable to detect a default project ID.
+```
+
+## The client library
+
+This sample uses the [Google Cloud Compute Client Library for PHP][google-cloud-php].
+You can read the documentation for more details on API usage and use GitHub
+to [browse the source][google-cloud-php-source] and [report issues][google-cloud-php-issues].
+
+[google-cloud-php]: https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.152.0/compute/readme
+[google-cloud-php-source]: https://github.com/GoogleCloudPlatform/google-cloud-php
+[google-cloud-php-issues]: https://github.com/GoogleCloudPlatform/google-cloud-php/issues
+[google-cloud-sdk]: https://cloud.google.com/sdk/
diff --git a/compute/cloud-client/instances/composer.json b/compute/cloud-client/instances/composer.json
new file mode 100644
index 0000000000..8cca083542
--- /dev/null
+++ b/compute/cloud-client/instances/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "google/cloud-compute": "^0.1.0"
+ }
+}
diff --git a/compute/cloud-client/instances/phpunit.xml.dist b/compute/cloud-client/instances/phpunit.xml.dist
new file mode 100644
index 0000000000..e3f3b067ee
--- /dev/null
+++ b/compute/cloud-client/instances/phpunit.xml.dist
@@ -0,0 +1,34 @@
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ src
+
+ ./vendor
+
+
+
+
diff --git a/compute/cloud-client/instances/src/create_instance.php b/compute/cloud-client/instances/src/create_instance.php
new file mode 100644
index 0000000000..31310ea9c7
--- /dev/null
+++ b/compute/cloud-client/instances/src/create_instance.php
@@ -0,0 +1,85 @@
+setSourceImage($sourceImage);
+ $disk = (new AttachedDisk())
+ ->setBoot(true)
+ ->setInitializeParams($diskInitializeParams);
+
+ // Set the network
+ $network = (new NetworkInterface())
+ ->setName($networkName);
+
+ // Create the Instance message
+ $instance = (new Instance())
+ ->setName($instanceName)
+ ->setDisks([$disk])
+ ->setMachineType($machineTypeFullName)
+ ->setNetworkInterfaces([$network]);
+
+ // Insert the new Compute Engine instance using the InstancesClient
+ $instancesClient = new InstancesClient();
+ $operation = $instancesClient->insert($instance, $projectId, $zone);
+
+ /** TODO: wait until operation completes */
+
+ printf('Created instance %s' . PHP_EOL, $instanceName);
+}
+
+require_once __DIR__ . '/../../../../testing/sample_helpers.php';
+\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv);
diff --git a/compute/cloud-client/instances/src/delete_instance.php b/compute/cloud-client/instances/src/delete_instance.php
new file mode 100644
index 0000000000..c9d5def504
--- /dev/null
+++ b/compute/cloud-client/instances/src/delete_instance.php
@@ -0,0 +1,54 @@
+delete($instanceName, $projectId, $zone);
+
+ /** TODO: wait until operation completes */
+
+ printf('Deleted instance %s' . PHP_EOL, $instanceName);
+}
+
+require_once __DIR__ . '/../../../../testing/sample_helpers.php';
+\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv);
diff --git a/compute/cloud-client/instances/src/list_instances.php b/compute/cloud-client/instances/src/list_instances.php
new file mode 100644
index 0000000000..671053bc48
--- /dev/null
+++ b/compute/cloud-client/instances/src/list_instances.php
@@ -0,0 +1,51 @@
+list_($projectId, $zone);
+
+ printf('Instances for %s (%s)' . PHP_EOL, $projectId, $zone);
+ foreach ($instancesList as $instance) {
+ printf(' - %s' . PHP_EOL, $instance->getName());
+ }
+}
+
+require_once __DIR__ . '/../../../../testing/sample_helpers.php';
+\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv);
diff --git a/compute/cloud-client/instances/test/instancesTest.php b/compute/cloud-client/instances/test/instancesTest.php
new file mode 100644
index 0000000000..b10dd73075
--- /dev/null
+++ b/compute/cloud-client/instances/test/instancesTest.php
@@ -0,0 +1,70 @@
+runFunctionSnippet('create_instance', [
+ 'projectId' => self::$projectId,
+ 'zone' => self::DEFAULT_ZONE,
+ 'instanceName' => self::$instanceName
+ ]);
+ $this->assertStringContainsString('Created instance ' . self::$instanceName, $output);
+ }
+
+ /**
+ * @depends testCreateInstance
+ */
+ public function testListInstances()
+ {
+ $output = $this->runFunctionSnippet('list_instances', [
+ 'projectId' => self::$projectId,
+ 'zone' => self::DEFAULT_ZONE,
+ ]);
+ $this->assertStringContainsString(self::$instanceName, $output);
+ }
+
+ /**
+ * @depends testCreateInstance
+ */
+ public function testDeleteInstance()
+ {
+ $output = $this->runFunctionSnippet('delete_instance', [
+ 'projectId' => self::$projectId,
+ 'zone' => self::DEFAULT_ZONE,
+ 'instanceName' => self::$instanceName,
+ ]);
+ $this->assertStringContainsString('Deleted instance ' . self::$instanceName, $output);
+ }
+}
diff --git a/testing/sample_helpers.php b/testing/sample_helpers.php
index 35166b8a71..8d76a848bd 100644
--- a/testing/sample_helpers.php
+++ b/testing/sample_helpers.php
@@ -43,7 +43,7 @@ function execute_sample(string $file, string $namespace, ?array $argv)
// If any parameters are typehinted as "array", explode user input on ","
$parameterReflections = $functionReflection->getParameters();
- foreach ($argv as $i => $val) {
+ foreach (array_values($argv) as $i => $val) {
$parameterReflection = $parameterReflections[$i];
if (
$parameterReflection->hasType()
From 9aaf3af6faeb4d6c605b5a62db3217f0a4fb5feb Mon Sep 17 00:00:00 2001
From: ace-n
Date: Tue, 30 Mar 2021 17:04:52 -0700
Subject: [PATCH 003/624] Add sub-directory support
---
.kokoro/system_tests.sh | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh
index a577256234..8df2a8208d 100755
--- a/.kokoro/system_tests.sh
+++ b/.kokoro/system_tests.sh
@@ -64,5 +64,11 @@ fi
# Install global test dependencies
composer install -d testing/
+# cd into specific subdirectory (if appropriate)
+MAIN_DIR=$(pwd)
+if [[ -z "${SUB_DIRECTORY}" ]]; then
+ cd $SUB_DIRECTORY
+fi
+
# Run tests
-bash testing/run_test_suite.sh
+bash MAIN_DIR/testing/run_test_suite.sh
From 7f1cc6b89dab9564faf61e46c90af1b667fdd535 Mon Sep 17 00:00:00 2001
From: ace-n
Date: Tue, 30 Mar 2021 17:07:34 -0700
Subject: [PATCH 004/624] Split lint out into separate build
---
.kokoro/lint.cfg | 12 ++++++++++++
.kokoro/lint.sh | 33 +++++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+)
create mode 100644 .kokoro/lint.cfg
create mode 100755 .kokoro/lint.sh
diff --git a/.kokoro/lint.cfg b/.kokoro/lint.cfg
new file mode 100644
index 0000000000..160ca627cb
--- /dev/null
+++ b/.kokoro/lint.cfg
@@ -0,0 +1,12 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Configure the docker image for kokoro-trampoline.
+env_vars: {
+ key: "TRAMPOLINE_IMAGE"
+ value: "gcr.io/cloud-devrel-kokoro-resources/php74"
+}
+
+env_vars: {
+ key: "RUN_CS_CHECK"
+ value: "true"
+}
diff --git a/.kokoro/lint.sh b/.kokoro/lint.sh
new file mode 100755
index 0000000000..b8c2df59de
--- /dev/null
+++ b/.kokoro/lint.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Copyright 2021 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+if [ "${BASH_DEBUG}" = "true" ]; then
+ set -x
+fi
+
+# Kokoro directory for running these samples
+cd github/php-docs-samples
+
+mkdir -p build/logs
+
+export PULL_REQUEST_NUMBER=$KOKORO_GITHUB_PULL_REQUEST_NUMBER
+
+# Run code standards check when appropriate
+if [ "${RUN_CS_CHECK}" = "true" ]; then
+ bash testing/run_cs_check.sh
+fi
From 78e4be08e5de824cd9e82a48da5968b5be361690 Mon Sep 17 00:00:00 2001
From: ace-n
Date: Tue, 30 Mar 2021 17:10:09 -0700
Subject: [PATCH 005/624] Fix typo
---
.kokoro/system_tests.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh
index 8df2a8208d..2a2c6a0183 100755
--- a/.kokoro/system_tests.sh
+++ b/.kokoro/system_tests.sh
@@ -71,4 +71,4 @@ if [[ -z "${SUB_DIRECTORY}" ]]; then
fi
# Run tests
-bash MAIN_DIR/testing/run_test_suite.sh
+bash $MAIN_DIR/testing/run_test_suite.sh
From ee9dd43fd8b4ff062601c453f7b5eaba46b9c3b9 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Wed, 31 Mar 2021 09:50:27 -0700
Subject: [PATCH 006/624] chore: replace deprecated silex with slim (#1296)
---
appengine/flexible/analytics/app.php | 40 ++++---
appengine/flexible/analytics/composer.json | 8 +-
appengine/flexible/analytics/index.php | 2 -
.../flexible/analytics/test/LocalTest.php | 31 +++---
appengine/flexible/datastore/app.php | 46 ++++----
appengine/flexible/datastore/composer.json | 6 +-
appengine/flexible/datastore/index.php | 2 -
.../flexible/datastore/test/LocalTest.php | 30 ++----
appengine/flexible/helloworld/composer.json | 7 +-
.../{ControllersTest.php => LocalTest.php} | 42 ++++----
appengine/flexible/helloworld/web/index.php | 20 +++-
appengine/flexible/logging/app.php | 62 ++++++-----
appengine/flexible/logging/composer.json | 7 +-
appengine/flexible/logging/index.php | 1 -
appengine/flexible/logging/phpunit.xml.dist | 2 +-
appengine/flexible/logging/test/LocalTest.php | 39 +++----
appengine/flexible/memcache/app.php | 102 +++++++++---------
appengine/flexible/memcache/composer.json | 12 +--
.../flexible/memcache/test/LocalTest.php | 101 +++++++++--------
appengine/flexible/memcache/web/index.php | 1 -
appengine/flexible/metadata/app.php | 21 ++--
appengine/flexible/metadata/composer.json | 3 +-
appengine/flexible/metadata/index.php | 1 -
appengine/flexible/staticcontent/app.php | 34 ++++--
.../flexible/staticcontent/composer.json | 9 +-
.../flexible/staticcontent/index.html.twig | 2 +-
.../flexible/staticcontent/web/index.php | 1 -
appengine/flexible/storage/app.php | 57 ++++++----
appengine/flexible/storage/composer.json | 6 +-
appengine/flexible/storage/index.php | 11 +-
appengine/flexible/storage/test/LocalTest.php | 64 +++++------
appengine/flexible/tasks/composer.json | 3 +-
appengine/flexible/tasks/index.php | 24 +++--
appengine/flexible/tasks/test/LocalTest.php | 44 +++-----
appengine/flexible/twilio/app.php | 78 ++++++++------
appengine/flexible/twilio/composer.json | 9 +-
appengine/flexible/twilio/index.php | 5 -
appengine/flexible/twilio/test/LocalTest.php | 63 +++++------
appengine/flexible/websockets/README.md | 6 +-
appengine/flexible/websockets/composer.json | 8 +-
appengine/flexible/websockets/index.php | 44 --------
.../test/{system_test.php => LocalTest.php} | 22 ++--
.../standard/getting-started/composer.json | 6 +-
.../standard/getting-started/src/app.php | 31 +++---
.../getting-started/src/controllers.php | 55 +++++-----
.../getting-started/test/ControllersTest.php | 15 +--
cloud_sql/mysql/pdo/tests/IntegrationTest.php | 3 +-
.../postgres/pdo/tests/IntegrationTest.php | 2 +-
debugger/README.md | 4 +-
debugger/composer.json | 8 +-
debugger/views/hello.html.twig | 4 +-
debugger/web/index.php | 29 +++--
dlp/quickstart.php | 3 +-
endpoints/getting-started/app.php | 60 ++++++-----
endpoints/getting-started/composer.json | 7 +-
endpoints/getting-started/index.php | 1 -
endpoints/getting-started/test/LocalTest.php | 36 +++----
pubsub/app/README.md | 2 +-
pubsub/app/app.php | 67 +++++++-----
pubsub/app/app.yaml | 14 +--
pubsub/app/composer.json | 12 +--
pubsub/app/index.php | 8 +-
pubsub/app/test/appTest.php | 57 ++++------
renovate.json | 3 -
testing/run_test_suite.sh | 16 ---
trace/trace-sample.php | 4 +-
66 files changed, 732 insertions(+), 791 deletions(-)
rename appengine/flexible/helloworld/test/{ControllersTest.php => LocalTest.php} (50%)
delete mode 100644 appengine/flexible/websockets/index.php
rename appengine/flexible/websockets/test/{system_test.php => LocalTest.php} (77%)
diff --git a/appengine/flexible/analytics/app.php b/appengine/flexible/analytics/app.php
index 6d84742663..16c21acdea 100644
--- a/appengine/flexible/analytics/app.php
+++ b/appengine/flexible/analytics/app.php
@@ -16,19 +16,26 @@
*/
use GuzzleHttp\Client;
-use Silex\Application;
-use Silex\Provider\TwigServiceProvider;
-use Symfony\Component\HttpFoundation\Request;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
+use Slim\Views\TwigMiddleware;
-// create the Silex application
-$app = new Application();
-$app->register(new TwigServiceProvider());
-$app['twig.path'] = [ __DIR__ ];
+// Create App
+$app = AppFactory::create();
-$app->get('/', function (Application $app, Request $request) {
- /** @var Twig_Environment $twig */
- $twig = $app['twig'];
- $trackingId = $app['GA_TRACKING_ID'];
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+// Create Twig
+$twig = Twig::create(__DIR__);
+
+// Add Twig-View Middleware
+$app->add(TwigMiddleware::create($app, $twig));
+
+$app->get('/', function (Request $request, Response $response) use ($twig) {
+ $trackingId = getenv('GA_TRACKING_ID');
# [START gae_flex_analytics_track_event]
$baseUri = 'http://www.google-analytics.com/';
$client = new GuzzleHttp\Client(['base_uri' => $baseUri]);
@@ -44,12 +51,13 @@
'el' => 'Hearts', # Event label.
'ev' => 0, # Event value, must be an integer
];
- $response = $client->request('POST', 'collect', ['form_params' => $formData]);
+ $gaResponse = $client->request('POST', 'collect', ['form_params' => $formData]);
# [END gae_flex_analytics_track_event]
- return $twig->render('index.html.twig', [
- 'base_uri' => $baseUri,
- 'response_code' => $response->getStatusCode(),
- 'response_reason' => $response->getReasonPhrase()]);
+ return $twig->render($response, 'index.html.twig', [
+ 'base_uri' => $baseUri,
+ 'response_code' => $gaResponse->getStatusCode(),
+ 'response_reason' => $gaResponse->getReasonPhrase()
+ ]);
});
return $app;
diff --git a/appengine/flexible/analytics/composer.json b/appengine/flexible/analytics/composer.json
index b267cd091a..50c1ea7a3c 100644
--- a/appengine/flexible/analytics/composer.json
+++ b/appengine/flexible/analytics/composer.json
@@ -1,8 +1,8 @@
{
"require": {
- "silex/silex": "^2.3",
- "twig/twig": "^1.24",
- "guzzlehttp/guzzle": "^7.0",
- "symfony/css-selector": "^3.1"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.2",
+ "guzzlehttp/guzzle": "^7.0"
}
}
diff --git a/appengine/flexible/analytics/index.php b/appengine/flexible/analytics/index.php
index 90a6c46e9b..3fc7a490f3 100644
--- a/appengine/flexible/analytics/index.php
+++ b/appengine/flexible/analytics/index.php
@@ -23,6 +23,4 @@
// Run the app!
// use "gcloud app deploy"
-$app['debug'] = true;
-$app['GA_TRACKING_ID'] = getenv('GA_TRACKING_ID');
$app->run();
diff --git a/appengine/flexible/analytics/test/LocalTest.php b/appengine/flexible/analytics/test/LocalTest.php
index 3e3d9843bd..26915b8924 100644
--- a/appengine/flexible/analytics/test/LocalTest.php
+++ b/appengine/flexible/analytics/test/LocalTest.php
@@ -16,30 +16,25 @@
*/
namespace Google\Cloud\Samples\AppEngine\Analytics;
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- parent::setUp();
- $this->client = $this->createClient();
- }
+ use TestTrait;
- public function createApplication()
+ public function testIndex()
{
+ $this->requireEnv('GA_TRACKING_ID');
+
$app = require __DIR__ . '/../app.php';
- $app['GA_TRACKING_ID'] = getenv('GA_TRACKING_ID');
- return $app;
- }
- public function testIndex()
- {
// Access the modules app top page.
- $client = $this->client;
- $crawler = $client->request('GET', '/');
- $this->assertTrue($client->getResponse()->isOk());
- $this->assertEquals(1, $crawler->filter(
- 'html:contains("returned 200")')->count());
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $body = (string) $response->getBody();
+ $this->assertStringContainsString('returned 200', $body);
}
}
diff --git a/appengine/flexible/datastore/app.php b/appengine/flexible/datastore/app.php
index b9cb532fa5..f6d7b5bebf 100644
--- a/appengine/flexible/datastore/app.php
+++ b/appengine/flexible/datastore/app.php
@@ -16,29 +16,34 @@
*/
use Google\Cloud\Datastore\DatastoreClient;
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use RKA\Middleware\IpAddress;
+use Slim\Factory\AppFactory;
-// create the Silex application
-$app = new Application();
+// Create App
+$app = AppFactory::create();
+
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+// Add IP address middleware
+$checkProxyHeaders = true;
+$trustedProxies = ['10.0.0.1', '10.0.0.2'];
+$app->add(new IpAddress($checkProxyHeaders, $trustedProxies));
+
+$app->get('/', function (Request $request, Response $response) {
+ $projectId = getenv('GCLOUD_PROJECT');
+ if (empty($projectId)) {
+ $response->getBody()->write('Set the GCLOUD_PROJECT environment variable to run locally');
+ return $response;
+ }
-$app['datastore'] = function () use ($app) {
- $projectId = $app['project_id'];
# [START gae_flex_datastore_client]
$datastore = new DatastoreClient([
'projectId' => $projectId
]);
# [END gae_flex_datastore_client]
- return $datastore;
-};
-
-$app->get('/', function (Application $app, Request $request) {
- if (empty($app['project_id'])) {
- return 'Set the GCLOUD_PROJECT environment variable to run locally';
- }
- /** @var \Google_Service_Datastore $datastore */
- $datastore = $app['datastore'];
// determine the user's IP
$user_ip = get_user_ip($request);
@@ -68,13 +73,16 @@
}
# [END gae_flex_datastore_query]
array_unshift($visits, "Last 10 visits:");
- return new Response(implode("\n", $visits), 200,
- ['Content-Type' => 'text/plain']);
+ $response->getBody()->write(implode("\n", $visits));
+
+ return $response
+ ->withStatus(200)
+ ->withHeader('Content-Type', 'text/plain');
});
function get_user_ip(Request $request)
{
- $ip = $request->GetClientIp();
+ $ip = $request->getAttribute('ip_address');
// Keep only the first two octets of the IP address.
$octets = explode($separator = ':', $ip);
if (count($octets) < 2) { // Must be ip4 address
diff --git a/appengine/flexible/datastore/composer.json b/appengine/flexible/datastore/composer.json
index e26f7aac31..dfb1e10bbe 100644
--- a/appengine/flexible/datastore/composer.json
+++ b/appengine/flexible/datastore/composer.json
@@ -1,6 +1,8 @@
{
"require": {
- "silex/silex": "^2.3",
- "google/cloud-datastore": "^1.0"
+ "google/cloud-datastore": "^1.0",
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "akrabat/ip-address-middleware": "^2.0"
}
}
diff --git a/appengine/flexible/datastore/index.php b/appengine/flexible/datastore/index.php
index 1fe28e68c2..3fc7a490f3 100644
--- a/appengine/flexible/datastore/index.php
+++ b/appengine/flexible/datastore/index.php
@@ -23,6 +23,4 @@
// Run the app!
// use "gcloud app deploy"
-$app['debug'] = true;
-$app['project_id'] = getenv('GCLOUD_PROJECT');
$app->run();
diff --git a/appengine/flexible/datastore/test/LocalTest.php b/appengine/flexible/datastore/test/LocalTest.php
index 1d13280fad..137a53836a 100644
--- a/appengine/flexible/datastore/test/LocalTest.php
+++ b/appengine/flexible/datastore/test/LocalTest.php
@@ -16,33 +16,23 @@
*/
namespace Google\Cloud\Test;
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- parent::setUp();
- $this->client = $this->createClient();
- }
+ use TestTrait;
- public function createApplication()
+ public function testIndex()
{
$app = require __DIR__ . '/../app.php';
- if (!$projectId = getenv('GCLOUD_PROJECT')) {
- $this->markTestSkipped('Must set GCLOUD_PROJECT');
- }
- $app['project_id'] = $projectId;
- return $app;
- }
- public function testIndex()
- {
// Access the modules app top page.
- $client = $this->client;
- $client->request('GET', '/');
- $this->assertTrue($client->getResponse()->isOk());
- $text = $client->getResponse()->getContent();
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $text = (string) $response->getBody();
$this->assertStringContainsString("Last 10 visits:", $text);
}
}
diff --git a/appengine/flexible/helloworld/composer.json b/appengine/flexible/helloworld/composer.json
index 684d676bc8..b3ba89a446 100644
--- a/appengine/flexible/helloworld/composer.json
+++ b/appengine/flexible/helloworld/composer.json
@@ -1,9 +1,6 @@
{
"require": {
- "silex/silex": "^2.3"
- },
- "require-dev": {
- "symfony/browser-kit": "^4.4",
- "symfony/http-kernel": "^4.4"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3"
}
}
diff --git a/appengine/flexible/helloworld/test/ControllersTest.php b/appengine/flexible/helloworld/test/LocalTest.php
similarity index 50%
rename from appengine/flexible/helloworld/test/ControllersTest.php
rename to appengine/flexible/helloworld/test/LocalTest.php
index 8bab32af9d..73582de378 100644
--- a/appengine/flexible/helloworld/test/ControllersTest.php
+++ b/appengine/flexible/helloworld/test/LocalTest.php
@@ -18,37 +18,33 @@
namespace Google\Cloud\Samples\Bookshelf;
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-/**
- * Test for application controllers
- */
-class ControllersTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function createApplication()
- {
- $app = require __DIR__ . '/../web/index.php';
- $app['debug'] = true;
- unset($app['exception_handler']);
-
- return $app;
- }
+ use TestTrait;
public function testTopPage()
{
- $client = $this->createClient();
- $crawlerexport = $client->request('GET', '/');
- $resp = $client->getResponse();
- $this->assertTrue($resp->isOk());
- $this->assertStringContainsString('Hello World', $resp->getContent());
+ $app = require __DIR__ . '/../web/index.php';
+
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $body = (string) $response->getBody();
+ $this->assertStringContainsString('Hello World', $body);
}
public function testGoodbye()
{
- $client = $this->createClient();
- $crawlerexport = $client->request('GET', '/goodbye');
- $resp = $client->getResponse();
- $this->assertTrue($resp->isOk());
- $this->assertStringContainsString('Goodbye World', $resp->getContent());
+ $app = require __DIR__ . '/../web/index.php';
+
+ $request = (new RequestFactory)->createRequest('GET', '/goodbye');
+ $response = $app->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $body = (string) $response->getBody();
+ $this->assertStringContainsString('Goodbye World', $body);
}
}
diff --git a/appengine/flexible/helloworld/web/index.php b/appengine/flexible/helloworld/web/index.php
index 61b7f0f586..73700b45eb 100644
--- a/appengine/flexible/helloworld/web/index.php
+++ b/appengine/flexible/helloworld/web/index.php
@@ -19,14 +19,24 @@
// [START appengine_flex_helloworld_index_php]
require_once __DIR__ . '/../vendor/autoload.php';
-$app = new Silex\Application();
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
-$app->get('/', function () {
- return 'Hello World';
+// Create App
+$app = AppFactory::create();
+
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$app->get('/', function (Request $request, Response $response) {
+ $response->getBody()->write('Hello World');
+ return $response;
});
-$app->get('/goodbye', function () {
- return 'Goodbye World';
+$app->get('/goodbye', function (Request $request, Response $response) {
+ $response->getBody()->write('Goodbye World');
+ return $response;
});
// @codeCoverageIgnoreStart
diff --git a/appengine/flexible/logging/app.php b/appengine/flexible/logging/app.php
index 08a2b6caf1..5654298625 100644
--- a/appengine/flexible/logging/app.php
+++ b/appengine/flexible/logging/app.php
@@ -18,38 +18,45 @@
# [START creating_psr3_logger_import]
use Google\Cloud\Logging\LoggingClient;
# [END creating_psr3_logger_import]
-use Silex\Application;
-use Silex\Provider\TwigServiceProvider;
-use Symfony\Component\HttpFoundation\Request;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
+use Slim\Views\TwigMiddleware;
-// create the Silex application
-$app = new Application();
-$app['project_id'] = getenv('GOOGLE_PROJECT_ID');
-// register twig
-$app->register(new TwigServiceProvider(), [
- 'twig.path' => __DIR__
-]);
+// Create App
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
- if (empty($app['project_id'])) {
- return 'Set the GOOGLE_PROJECT_ID environment variable to run locally';
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+// Create Twig
+$twig = Twig::create(__DIR__);
+$app->add(TwigMiddleware::create($app, $twig));
+
+$projectId = getenv('GCLOUD_PROJECT');
+
+$app->get('/', function (Request $request, Response $response) use ($projectId, $twig) {
+ if (empty($projectId)) {
+ $response->getBody()->write('Set the GCLOUD_PROJECT environment variable to run locally');
+ return $response;
}
- $projectId = $app['project_id'];
$logging = new LoggingClient([
'projectId' => $projectId
]);
- $logger = $logging->logger('logging-sample');
+ $logger = $logging->logger('app');
+ $oneDayAgo = (new \DateTime('-1 day'))->format('c'); // ISO-8061
$logs = $logger->entries([
'pageSize' => 10,
'resultLimit' => 10,
- 'orderBy' => 'timestamp desc'
+ 'orderBy' => 'timestamp desc',
+ 'filter' => sprintf('timestamp >= "%s"', $oneDayAgo),
]);
- return $app['twig']->render('index.html.twig', ['logs' => $logs]);
+ return $twig->render($response, 'index.html.twig', ['logs' => $logs]);
});
-$app->post('/log', function (Request $request) use ($app) {
- $projectId = $app['project_id'];
- $text = $request->get('text');
+$app->post('/log', function (Request $request, Response $response) use ($projectId) {
+ parse_str((string) $request->getBody(), $postData);
# [START gae_flex_configure_logging]
# [START creating_psr3_logger]
$logging = new LoggingClient([
@@ -57,15 +64,15 @@
]);
$logger = $logging->psrLogger('app');
# [END creating_psr3_logger]
- $logger->notice($text);
+ $logger->notice($postData['text'] ?? '');
# [END gae_flex_configure_logging]
- return $app->redirect('/');
+ return $response
+ ->withHeader('Location', '/')
+ ->withStatus(302);
});
-$app->get('/async_log', function (Request $request) use ($app) {
- $token = $request->query->get('token');
- $projectId = $app['project_id'];
- $text = $request->get('text');
+$app->get('/async_log', function (Request $request, Response $response) use ($projectId) {
+ $token = $request->getUri()->getQuery('token');
# [START enabling_batch]
$logger = LoggingClient::psrBatchLogger('app');
# [END enabling_batch]
@@ -74,7 +81,8 @@
$logger->error('Oh no');
# [END using_the_logger]
$logger->info("Token: $token");
- return 'Sent some logs';
+ $response->getBody()->write('Sent some logs');
+ return $response;
});
return $app;
diff --git a/appengine/flexible/logging/composer.json b/appengine/flexible/logging/composer.json
index 19db2b7281..db518467f2 100644
--- a/appengine/flexible/logging/composer.json
+++ b/appengine/flexible/logging/composer.json
@@ -1,8 +1,9 @@
{
"require": {
- "google/cloud-logging": "^1.20.0",
+ "google/cloud-logging": "^1.21.0",
"google/cloud-error-reporting": "^0.16.2",
- "silex/silex": "^2.0",
- "twig/twig": "^1.29"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.2"
}
}
diff --git a/appengine/flexible/logging/index.php b/appengine/flexible/logging/index.php
index 546488d2b2..d8dfc5ea3e 100644
--- a/appengine/flexible/logging/index.php
+++ b/appengine/flexible/logging/index.php
@@ -19,5 +19,4 @@
$app = require_once __DIR__ . '/app.php';
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/logging/phpunit.xml.dist b/appengine/flexible/logging/phpunit.xml.dist
index ac9bc23409..6c24c63aee 100644
--- a/appengine/flexible/logging/phpunit.xml.dist
+++ b/appengine/flexible/logging/phpunit.xml.dist
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
test
diff --git a/appengine/flexible/logging/test/LocalTest.php b/appengine/flexible/logging/test/LocalTest.php
index 132a10f25d..14a85694a6 100644
--- a/appengine/flexible/logging/test/LocalTest.php
+++ b/appengine/flexible/logging/test/LocalTest.php
@@ -16,40 +16,33 @@
*/
namespace Google\Cloud\Test;
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- if (!getenv('GOOGLE_PROJECT_ID')) {
- $this->markTestSkipped('Must set GOOGLE_PROJECT_ID');
- }
- parent::setUp();
- $this->client = $this->createClient();
- }
+ use TestTrait;
- public function createApplication()
+ public function testSomeLogs()
{
$app = require __DIR__ . '/../app.php';
- $app['project_id'] = getenv('GOOGLE_PROJECT_ID');
- $app['debug'] = true;
- return $app;
- }
- public function testSomeLogs()
- {
- $this->client->request('GET', '/');
- $response = $this->client->getResponse();
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request);
+
$this->assertEquals(200, $response->getStatusCode());
- $text = $response->getContent();
+ $text = (string) $response->getBody();
$this->assertStringContainsString("Logs:", $text);
}
public function testAsyncLog()
{
- $this->client->request('GET', '/async_log');
- $response = $this->client->getResponse();
- $this->assertTrue($response->isOk());
+ $app = require __DIR__ . '/../app.php';
+
+ $request = (new RequestFactory)->createRequest('GET', '/async_log');
+ $response = $app->handle($request);
+
+ $this->assertEquals(200, $response->getStatusCode());
}
}
diff --git a/appengine/flexible/memcache/app.php b/appengine/flexible/memcache/app.php
index 133bbe4b9a..37d37b2300 100644
--- a/appengine/flexible/memcache/app.php
+++ b/appengine/flexible/memcache/app.php
@@ -15,16 +15,25 @@
* limitations under the License.
*/
-use Silex\Application;
-use Silex\Provider\TwigServiceProvider;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
+use DI\Container;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
-// create the Silex application
-$app = new Application();
-$app->register(new TwigServiceProvider());
-$app['twig.path'] = [ __DIR__ ];
-$app['memcached'] = function () {
+// Create the container
+AppFactory::setContainer($container = new Container());
+$container->set('view', function () {
+ return Twig::create(__DIR__);
+});
+
+// Create App
+$app = AppFactory::create();
+
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$container->set('memcached', function () {
# [START gae_flex_redislabs_memcache]
$endpoint = getenv('MEMCACHE_ENDPOINT');
$username = getenv('MEMCACHE_USERNAME');
@@ -40,78 +49,73 @@
}
# [END gae_flex_redislabs_memcache]
return $memcached;
-};
+});
-$app->get('/vars', function () {
- $vars = array('MEMCACHE_PORT_11211_TCP_ADDR',
- 'MEMCACHE_PORT_11211_TCP_PORT');
+$app->get('/vars', function (Request $request, Response $response) {
+ $vars = [
+ 'MEMCACHE_PORT_11211_TCP_ADDR',
+ 'MEMCACHE_PORT_11211_TCP_PORT'
+ ];
$lines = array();
foreach ($vars as $var) {
$val = getenv($var);
array_push($lines, "$var = $val");
}
- return new Response(
- implode("\n", $lines),
- 200,
- ['Content-Type' => 'text/plain']);
+ $response->getBody()->write(implode("\n", $lines));
+ return $response->withHeader('Content-Type', 'text/plain');
});
-$app->get('/', function (Application $app, Request $request) {
- /** @var Twig_Environment $twig */
- $twig = $app['twig'];
- /** @var Memcached $memcached */
- $memcached = $app['memcached'];
- return $twig->render('memcache.html.twig', [
+$app->get('/', function (Request $request, Response $response) use ($container) {
+ $memcached = $container->get('memcached');
+ return $container->get('view')->render($response, 'memcache.html.twig', [
'who' => $memcached->get('who'),
'count' => $memcached->get('count'),
- 'host' => $request->getHttpHost(),
+ 'host' => $request->getUri()->getHost(),
]);
});
-$app->post('/reset', function (Application $app, Request $request) {
- /** @var Twig_Environment $twig */
- $twig = $app['twig'];
- /** @var Memcached $memcached */
- $memcached = $app['memcached'];
+$app->post('/reset', function (Request $request, Response $response) use ($container) {
+ $memcached = $container->get('memcached');
$memcached->delete('who');
$memcached->set('count', 0);
- return $twig->render('memcache.html.twig', [
- 'host' => $request->getHttpHost(),
+ return $container->get('view')->render($response, 'memcache.html.twig', [
+ 'host' => $request->getUri()->getHost(),
'count' => 0,
'who' => '',
]);
});
-$app->post('/', function (Application $app, Request $request) {
- /** @var Twig_Environment $twig */
- $twig = $app['twig'];
- /** @var Memcached $memcached */
- $memcached = $app['memcached'];
- $memcached->set('who', $request->get('who'));
+$app->post('/', function (Request $request, Response $response) use ($container) {
+ parse_str((string) $request->getBody(), $postData);
+ $who = $postData['who'] ?? '';
+ $memcached = $container->get('memcached');
+ $memcached->set('who', $who);
$count = $memcached->increment('count');
if (false === $count) {
// Potential race condition. Use binary protocol to avoid.
$memcached->set('count', 0);
$count = 0;
}
- return $twig->render('memcache.html.twig', [
- 'who' => $request->get('who'),
+ return $container->get('view')->render($response, 'memcache.html.twig', [
+ 'who' => $who,
'count' => $count,
- 'host' => $request->getHttpHost(),
+ 'host' => $request->getUri()->getHost(),
]);
});
-$app->get('/memcached/{key}', function (Application $app, $key) {
- /** @var Memcached $memcached */
- $memcached = $app['memcached'];
- return $memcached->get($key);
+$app->get('/memcached/{key}', function (Request $request, Response $response, $args) use ($container) {
+ $memcached = $container->get('memcached');
+ $value = $memcached->get($args['key']);
+ $response->getBody()->write((string) $value);
+ return $response;
});
-$app->put('/memcached/{key}', function (Application $app, $key, Request $request) {
- /** @var Memcached $memcached */
- $memcached = $app['memcached'];
- $value = $request->getContent();
- return $memcached->set($key, $value, time() + 600); // 10 minutes expiration
+$app->put('/memcached/{key}', function (Request $request, Response $response, $args) use ($container) {
+ $memcached = $container->get('memcached');
+ $value = (string) $request->getBody();
+ $success = $memcached->set($args['key'], $value, time() + 600); // 10 minutes expiration
+ $response->getBody()->write((string) $success);
+ return $response;
});
return $app;
diff --git a/appengine/flexible/memcache/composer.json b/appengine/flexible/memcache/composer.json
index 2217960ba0..7fa5d51921 100644
--- a/appengine/flexible/memcache/composer.json
+++ b/appengine/flexible/memcache/composer.json
@@ -1,12 +1,8 @@
{
"require": {
- "silex/silex": "^2.3",
- "twig/twig": "^1.24"
- },
- "require-dev": {
- "gecko-packages/gecko-memcache-mock": "^2.0",
- "paragonie/random_compat": "^2.0",
- "monolog/monolog": "^1.19",
- "symfony/yaml": "~3.0|~4.0"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.2",
+ "php-di/slim-bridge": "^3.1"
}
}
diff --git a/appengine/flexible/memcache/test/LocalTest.php b/appengine/flexible/memcache/test/LocalTest.php
index b38be8024b..6a9e090ca2 100644
--- a/appengine/flexible/memcache/test/LocalTest.php
+++ b/appengine/flexible/memcache/test/LocalTest.php
@@ -14,71 +14,82 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-namespace Google\Cloud\Test;
-use Silex\WebTestCase;
-use GeckoPackages\MemcacheMock\MemcachedMock;
+use PHPUnit\Framework\TestCase;
+use Slim\Psr7\Factory\RequestFactory;
+use Prophecy\Argument;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- parent::setUp();
- $this->client = $this->createClient();
- }
-
- public function createApplication()
+ public function testIndex()
{
$app = require __DIR__ . '/../app.php';
- $app['memcached'] = new MemcachedMock;
- $app['memcached']->addServer("localhost", 11211);
- return $app;
- }
- public function testIndex()
- {
+ $memcached = $this->prophesize(Memcached::class);
+ $container = $app->getContainer();
+ $container->set('memcached', $memcached->reveal());
+
// Access the modules app top page.
- $client = $this->client;
- $client->request('GET', '/');
- $this->assertTrue($client->getResponse()->isOk());
+ $request1 = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request1);
+ $this->assertEquals(200, $response->getStatusCode());
// Make sure it handles a POST request too, which will increment the
// counter.
- $this->client->request('POST', '/');
- $this->assertTrue($this->client->getResponse()->isOk());
+ $request2 = (new RequestFactory)->createRequest('POST', '/');
+ $response = $app->handle($request2);
+ $this->assertEquals(200, $response->getStatusCode());
}
public function testGetAndPut()
{
+ $app = require __DIR__ . '/../app.php';
+
+ $memcached = $this->prophesize(Memcached::class);
+ $memcached->set('testkey1', 'sour', Argument::type('int'))
+ ->willReturn(true);
+ $memcached->get('testkey1')
+ ->willReturn('sour');
+
+ $memcached->set('testkey2', 'sweet', Argument::type('int'))
+ ->willReturn(true);
+ $memcached->get('testkey2')
+ ->willReturn('sweet');
+
+ $container = $app->getContainer();
+ $container->set('memcached', $memcached->reveal());
+
// Use a random key to avoid colliding with simultaneous tests.
- $key = rand(0, 1000);
// Test the /memcached REST API.
- $this->put("/memcached/test$key", "sour");
- $this->assertEquals("sour", $this->get("/memcached/test$key"));
- $this->put("/memcached/test$key", "sweet");
- $this->assertEquals("sweet", $this->get("/memcached/test$key"));
- }
+ $request1 = (new RequestFactory)->createRequest('PUT', "/memcached/testkey1");
+ $request1->getBody()->write('sour');
+ $response1 = $app->handle($request1);
+ $this->assertEquals(200, (string) $response1->getStatusCode());
- /**
- * HTTP PUTs the body to the url path.
- * @param $path string
- * @param $body string
- */
- private function put($path, $body)
- {
- $this->client->request('PUT', $path, array(), array(), array(), $body);
- return $this->client->getResponse()->getContent();
+ // Check that the key was written as expected
+ $request2 = (new RequestFactory)->createRequest('GET', "/memcached/testkey1");
+ $response2 = $app->handle($request2);
+ $this->assertEquals("sour", (string) $response2->getBody());
+
+ // Test the /memcached REST API with a new value.
+ $request3 = (new RequestFactory)->createRequest('PUT', "/memcached/testkey2");
+ $request3->getBody()->write('sweet');
+ $response3 = $app->handle($request3);
+ $this->assertEquals(200, (string) $response3->getStatusCode());
+
+ // Check that the key was written as expected
+ $request4 = (new RequestFactory)->createRequest('GET', "/memcached/testkey2");
+ $response4 = $app->handle($request4);
+ $this->assertEquals("sweet", (string) $response4->getBody());
}
+}
- /**
- * HTTP GETs the url path.
- * @param $path string
- * @return string The HTTP Response.
- */
- private function get($path)
+if (!class_exists('Memcached')) {
+ interface Memcached
{
- $this->client->request('GET', $path);
- return $this->client->getResponse()->getContent();
+ public function get($key);
+ public function set($key, $value, $timestamp = 0);
+ public function increment();
}
}
diff --git a/appengine/flexible/memcache/web/index.php b/appengine/flexible/memcache/web/index.php
index 71f345ee80..6c2543efa7 100644
--- a/appengine/flexible/memcache/web/index.php
+++ b/appengine/flexible/memcache/web/index.php
@@ -23,5 +23,4 @@
// Run the app!
// use "gcloud app deploy"
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/metadata/app.php b/appengine/flexible/metadata/app.php
index 319b5294c1..bc355f73c1 100644
--- a/appengine/flexible/metadata/app.php
+++ b/appengine/flexible/metadata/app.php
@@ -14,7 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-use Silex\Application;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
# [START gae_flex_metadata]
function get_external_ip_using_google_cloud()
@@ -39,21 +41,26 @@ function get_external_ip_using_curl()
}
# [END gae_flex_metadata]
-// create the Silex application
-$app = new Application();
+// Create App
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$app->get('/', function (Request $request, Response $response) {
if (!$externalIp = get_external_ip_using_google_cloud()) {
return 'Unable to reach Metadata server - are you running locally?';
}
- return sprintf('External IP: %s', $externalIp);
+ $response->getBody()->write(sprintf('External IP: %s', $externalIp));
+ return $response;
});
-$app->get('/curl', function () use ($app) {
+$app->get('/curl', function (Request $request, Response $response) {
if (!$externalIp = get_external_ip_using_curl()) {
return 'Unable to reach Metadata server - are you running locally?';
}
- return sprintf('External IP: %s', $externalIp);
+ $response->getBody()->write(sprintf('External IP: %s', $externalIp));
+ return $response;
});
return $app;
diff --git a/appengine/flexible/metadata/composer.json b/appengine/flexible/metadata/composer.json
index 7f021cf5c1..e5c6a01272 100644
--- a/appengine/flexible/metadata/composer.json
+++ b/appengine/flexible/metadata/composer.json
@@ -1,6 +1,7 @@
{
"require": {
- "silex/silex": "^2.3",
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
"google/cloud-core": "^1.5"
}
}
diff --git a/appengine/flexible/metadata/index.php b/appengine/flexible/metadata/index.php
index f7c85ebd9c..726d166977 100644
--- a/appengine/flexible/metadata/index.php
+++ b/appengine/flexible/metadata/index.php
@@ -23,5 +23,4 @@
// Run the app!
// use "gcloud app deploy"
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/staticcontent/app.php b/appengine/flexible/staticcontent/app.php
index bb0b7948f8..4cd55d24b0 100644
--- a/appengine/flexible/staticcontent/app.php
+++ b/appengine/flexible/staticcontent/app.php
@@ -15,20 +15,32 @@
* limitations under the License.
*/
-use Silex\Application;
-use Silex\Provider\TwigServiceProvider;
-use Symfony\Component\HttpFoundation\Request;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
+use Slim\Views\TwigMiddleware;
+use RKA\Middleware\IpAddress;
-// create the Silex application
-$app = new Application();
-$app->register(new TwigServiceProvider());
-$app['twig.path'] = [ __DIR__ ];
+// Create App
+$app = AppFactory::create();
-$app->get('/', function (Application $app, Request $request) {
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+// Create Twig
+$twig = Twig::create(__DIR__);
+$app->add(TwigMiddleware::create($app, $twig));
+
+// Add IP address middleware
+$checkProxyHeaders = true;
+$trustedProxies = ['10.0.0.1', '10.0.0.2'];
+$app->add(new IpAddress($checkProxyHeaders, $trustedProxies));
+
+$app->get('/', function (Request $request, Response $response) use ($twig) {
/** @var Twig_Environment $twig */
- $twig = $app['twig'];
- return $twig->render('index.html.twig',
- ['ip' => $request->getClientIps()[0]]);
+ return $twig->render($response, 'index.html.twig',
+ ['ip' => $request->getAttribute('ip_address')]);
});
return $app;
diff --git a/appengine/flexible/staticcontent/composer.json b/appengine/flexible/staticcontent/composer.json
index 2a25e4c325..a55125062f 100644
--- a/appengine/flexible/staticcontent/composer.json
+++ b/appengine/flexible/staticcontent/composer.json
@@ -1,9 +1,8 @@
{
"require": {
- "silex/silex": "^2.3",
- "twig/twig": "^1.24"
- },
- "require-dev": {
- "guzzlehttp/guzzle": "^6.3"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.2",
+ "akrabat/ip-address-middleware": "^2.0"
}
}
diff --git a/appengine/flexible/staticcontent/index.html.twig b/appengine/flexible/staticcontent/index.html.twig
index 5405693ad0..aa875dc856 100644
--- a/appengine/flexible/staticcontent/index.html.twig
+++ b/appengine/flexible/staticcontent/index.html.twig
@@ -2,7 +2,7 @@
Hello Static Content
This sample demonstrates how to serve static content with nginx, and
-dynamic content with silex.
+dynamic content with SlimPHP.
Enjoy this static image of trees:
diff --git a/appengine/flexible/staticcontent/web/index.php b/appengine/flexible/staticcontent/web/index.php
index 71f345ee80..6c2543efa7 100644
--- a/appengine/flexible/staticcontent/web/index.php
+++ b/appengine/flexible/staticcontent/web/index.php
@@ -23,5 +23,4 @@
// Run the app!
// use "gcloud app deploy"
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/storage/app.php b/appengine/flexible/storage/app.php
index 8ed781dce3..99111620d9 100644
--- a/appengine/flexible/storage/app.php
+++ b/appengine/flexible/storage/app.php
@@ -16,23 +16,31 @@
*/
# [START gae_flex_storage_app]
+use DI\Container;
use Google\Cloud\Storage\StorageClient;
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
-// create the Silex application
-$app = new Application();
+// Create App
+AppFactory::setContainer($container = new Container());
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$container = $app->getContainer();
+
+$app->get('/', function (Request $request, Response $response) use ($container) {
/** @var Google\Cloud\StorageClient */
- $storage = $app['storage'];
- $bucketName = $app['bucket_name'];
- $objectName = $app['object_name'];
+ $storage = $container->get('storage');
+ $bucketName = $container->get('bucket_name');
+ $objectName = $container->get('object_name');
$bucket = $storage->bucket($bucketName);
$object = $bucket->object($objectName);
$content = $object->exists() ? $object->downloadAsString() : '';
$escapedContent = htmlspecialchars($content);
- $form = <<getBody()->write(<<Storage Example
Write [docs]:
-EOF;
+EOF
+);
if ($content) {
- $form .= "Your content:
$escapedContent
";
+ $response->getBody()->write(
+ "Your content:
$escapedContent
"
+ );
}
- return $form;
+ return $response;
});
/**
* Write to a Storage bucket.
* @see https://cloud.google.com/appengine/docs/flexible/php/using-cloud-storage
*/
-$app->post('/write', function (Request $request) use ($app) {
+$app->post('/write', function (Request $request, Response $response) use ($container) {
/** @var Google\Cloud\StorageClient */
- $storage = $app['storage'];
- $bucketName = $app['bucket_name'];
- $objectName = $app['object_name'];
- $content = $request->get('content');
+ $storage = $container->get('storage');
+ $bucketName = $container->get('bucket_name');
+ $objectName = $container->get('object_name');
+ parse_str((string) $request->getBody(), $postData);
$metadata = ['contentType' => 'text/plain'];
- $storage->bucket($bucketName)->upload($content, [
+ $storage->bucket($bucketName)->upload($postData['content'] ?? '', [
'name' => $objectName,
'metadata' => $metadata,
]);
- return $app->redirect('/');
+ return $response
+ ->withStatus(302)
+ ->withHeader('Location', '/');
});
-$app['storage'] = function () use ($app) {
- $projectId = $app['project_id'];
+$container->set('storage', function () use ($container) {
+ $projectId = $container->get('project_id');
$storage = new StorageClient([
'projectId' => $projectId
]);
return $storage;
-};
+});
# [END gae_flex_storage_app]
return $app;
diff --git a/appengine/flexible/storage/composer.json b/appengine/flexible/storage/composer.json
index 8cb351de42..3681dab0ac 100644
--- a/appengine/flexible/storage/composer.json
+++ b/appengine/flexible/storage/composer.json
@@ -1,6 +1,8 @@
{
"require": {
- "silex/silex": "^2.3",
- "google/cloud-storage": "^1.0"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "google/cloud-storage": "^1.0",
+ "php-di/slim-bridge": "^3.1"
}
}
diff --git a/appengine/flexible/storage/index.php b/appengine/flexible/storage/index.php
index abe1c72498..9c5676da73 100644
--- a/appengine/flexible/storage/index.php
+++ b/appengine/flexible/storage/index.php
@@ -21,12 +21,14 @@
$app = require __DIR__ . '/app.php';
+$container = $app->getContainer();
+
// change this to your bucket name!
-$app['bucket_name'] = getenv('GOOGLE_STORAGE_BUCKET') ?: 'your-bucket-name';
-$app['project_id'] = getenv('GCLOUD_PROJECT');
-$app['object_name'] = 'hello.txt';
+$container->set('bucket_name', getenv('GOOGLE_STORAGE_BUCKET') ?: 'your-bucket-name');
+$container->set('project_id', getenv('GCLOUD_PROJECT'));
+$container->set('object_name', 'hello.txt');
-if ($app['bucket_name'] == 'your-bucket-name') {
+if ($container->get('bucket_name') == 'your-bucket-name') {
die('Replace <your-bucket-name> with the name of your '
. 'cloud storage bucket in app.yaml or set it as an '
. 'environment variable for local development.');
@@ -34,5 +36,4 @@
// Run the app!
// use "gcloud app deploy" or run locally with dev_appserver.py
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/storage/test/LocalTest.php b/appengine/flexible/storage/test/LocalTest.php
index f8825aeac8..8621a91451 100644
--- a/appengine/flexible/storage/test/LocalTest.php
+++ b/appengine/flexible/storage/test/LocalTest.php
@@ -14,65 +14,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- if (!getenv('GOOGLE_PROJECT_ID')) {
- $this->markTestSkipped('Must set GOOGLE_PROJECT_ID');
- }
- if (!getenv('GOOGLE_STORAGE_BUCKET')) {
- $this->markTestSkipped('Must set GOOGLE_STORAGE_BUCKET');
- }
- parent::setUp();
- $this->client = $this->createClient();
- }
+ use TestTrait;
- public function createApplication()
- {
- $app = require __DIR__ . '/../app.php';
+ private static $app;
- // set some parameters for testing
- $app['session.test'] = true;
- $app['debug'] = true;
+ public static function setUpBeforeClass(): void
+ {
+ self::requireEnv('GOOGLE_STORAGE_BUCKET');
- // prevent HTML error exceptions
- unset($app['exception_handler']);
+ $app = require __DIR__ . '/../app.php';
// the bucket name doesn't matter because we mock the stream wrapper
- $app['project_id'] = getenv('GOOGLE_PROJECT_ID');
- $app['bucket_name'] = getenv('GOOGLE_STORAGE_BUCKET');
- $app['object_name'] = 'appengine/hello_flex.txt';
+ $container = $app->getContainer();
+ $container->set('project_id', getenv('GOOGLE_PROJECT_ID'));
+ $container->set('bucket_name', getenv('GOOGLE_STORAGE_BUCKET'));
+ $container->set('object_name', 'appengine/hello_flex.txt');
- return $app;
+ self::$app = $app;
}
public function testHome()
{
- $client = $this->createClient();
-
- $crawler = $client->request('GET', '/');
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = self::$app->handle($request);
- $this->assertTrue($client->getResponse()->isOk());
+ $this->assertEquals(200, $response->getStatusCode());
}
public function testWrite()
{
- $client = $this->createClient();
-
$time = date('Y-m-d H:i:s');
- $crawler = $client->request('POST', '/write', [
+ $request = (new RequestFactory)->createRequest('POST', '/write');
+ $request->getBody()->write(http_build_query([
'content' => sprintf('doot doot (%s)', $time),
- ]);
+ ]));
+
+ $response = self::$app->handle($request);
- $response = $client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
+ $this->assertEquals('/', $response->getHeaderLine('Location'));
- $crawler = $client->followRedirect();
- $response = $client->getResponse();
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = self::$app->handle($request);
$this->assertEquals(200, $response->getStatusCode());
- $this->assertStringContainsString($time, $response->getContent());
+ $this->assertStringContainsString($time, (string) $response->getBody());
}
}
diff --git a/appengine/flexible/tasks/composer.json b/appengine/flexible/tasks/composer.json
index a4b539d8b6..e18063e496 100644
--- a/appengine/flexible/tasks/composer.json
+++ b/appengine/flexible/tasks/composer.json
@@ -1,7 +1,8 @@
{
"require": {
"symfony/console": "^3.0",
- "silex/silex": "^2.3",
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
"google/apiclient": "^2.1",
"google/cloud-logging": "^1.5"
},
diff --git a/appengine/flexible/tasks/index.php b/appengine/flexible/tasks/index.php
index bd79d511f7..4a3d3d2ba4 100644
--- a/appengine/flexible/tasks/index.php
+++ b/appengine/flexible/tasks/index.php
@@ -18,27 +18,33 @@
require_once __DIR__ . '/vendor/autoload.php';
-use Symfony\Component\HttpFoundation\Request;
use Google\Cloud\Logging\LoggingClient;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
-$app = new Silex\Application();
+// Create App
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
- return 'Hello World';
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$app->get('/', function (Request $request, Response $response) {
+ $response->getBody()->write('Hello World');
+ return $response;
});
-$app->post('/example_task_handler', function (Request $request) use ($app) {
+$app->post('/example_task_handler', function (Request $request, Response $response) {
$logging = new LoggingClient();
$logName = 'my-log';
$logger = $logging->logger($logName);
- $loggingText = sprintf('Received task with payload: %s', $request->getContent());
+ $loggingText = sprintf('Received task with payload: %s', $request->getBody());
$entry = $logger->entry($loggingText);
$logger->write($entry);
- return $loggingText;
+ $response->getBody()->write($loggingText);
+ return $response;
});
-$app['debug'] = true;
-
// for testing
if (getenv('PHPUNIT_TESTS') === '1') {
return $app;
diff --git a/appengine/flexible/tasks/test/LocalTest.php b/appengine/flexible/tasks/test/LocalTest.php
index f73e1657c5..607b3d1c38 100644
--- a/appengine/flexible/tasks/test/LocalTest.php
+++ b/appengine/flexible/tasks/test/LocalTest.php
@@ -14,45 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function setUp(): void
- {
- parent::setUp();
- $this->client = $this->createClient();
- }
-
- public function createApplication()
- {
- $app = require __DIR__ . '/../index.php';
-
- // set some parameters for testing
- $app['session.test'] = true;
- $app['debug'] = true;
-
- // prevent HTML error exceptions
- unset($app['exception_handler']);
-
- return $app;
- }
-
public function testHome()
{
- $client = $this->createClient();
- $crawler = $client->request('GET', '/');
- $response = $client->getResponse();
- $this->assertTrue($client->getResponse()->isOk());
- $this->assertStringContainsString('Hello World', $response->getContent());
+ $app = require __DIR__ . '/../index.php';
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $app->handle($request);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertStringContainsString('Hello World', $response->getBody());
}
public function testLogPayload()
{
- $client = $this->createClient();
- $crawler = $client->request('POST', '/example_task_handler', [], [], [], 'google');
- $response = $client->getResponse();
+ $app = require __DIR__ . '/../index.php';
+ $request = (new RequestFactory)->createRequest('POST', '/example_task_handler');
+ $request->getBody()->write('google');
+ $response = $app->handle($request);
$this->assertEquals(200, $response->getStatusCode());
- $this->assertStringContainsString(sprintf('Received task with payload: google'), $response->getContent());
+ $this->assertStringContainsString(sprintf('Received task with payload: google'), $response->getBody());
}
}
diff --git a/appengine/flexible/twilio/app.php b/appengine/flexible/twilio/app.php
index 62e65b1117..bcd16b8556 100644
--- a/appengine/flexible/twilio/app.php
+++ b/appengine/flexible/twilio/app.php
@@ -14,25 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Twilio\Rest\Client as TwilioClient;
+use Twilio\TwiML\VoiceResponse;
+use Twilio\TwiML\MessagingResponse;
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
+// Create App
+$app = AppFactory::create();
-$app = new Application();
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$twilioAccountSid = getenv('TWILIO_ACCOUNT_SID');
+$twilioAuthToken = getenv('TWILIO_AUTH_TOKEN');
+$twilioNumber = getenv('TWILIO_FROM_NUMBER');
# [START gae_flex_twilio_receive_call]
/***
* Answers a call and replies with a simple greeting.
*/
-$app->post('/call/receive', function () use ($app) {
- $response = new Services_Twilio_Twiml();
- $response->say('Hello from Twilio!');
- return new Response(
- (string)$response,
- 200,
- ['Content-Type' => 'application/xml']
- );
+$app->post('/call/receive', function (Request $request, Response $response) {
+ $twiml = new VoiceResponse();
+ $twiml->say('Hello from Twilio!');
+ $response->getBody()->write((string) $twiml);
+ return $response
+ ->withHeader('Content-Type', 'application/xml');
});
# [END gae_flex_twilio_receive_call]
@@ -40,17 +48,26 @@
/***
* Send an sms.
*/
-$app->post('/sms/send', function (Request $request) use ($app) {
- $twilio = new Services_Twilio(
- $app['twilio.account_sid'], // Your Twilio Account SID
- $app['twilio.auth_token'] // Your Twilio Auth Token
+$app->post('/sms/send', function (
+ Request $request,
+ Response $response
+) use ($twilioAccountSid, $twilioAuthToken, $twilioNumber) {
+ $twilio = new TwilioClient(
+ $twilioAccountSid, // Your Twilio Account SID
+ $twilioAuthToken // Your Twilio Auth Token
);
- $sms = $twilio->account->messages->sendMessage(
- $app['twilio.number'], // From this number
- $request->get('to'), // Send to this number
- 'Hello from Twilio!'
+ parse_str((string) $request->getBody(), $postData);
+ $sms = $twilio->messages->create(
+ $postData['to'] ?? '', // to this number
+ [
+ 'from' => $twilioNumber, // from this number
+ 'body' => 'Hello from Twilio!',
+ ]
);
- return sprintf('Message ID: %s, Message Body: %s', $sms->sid, $sms->body);
+ $response->getBody()->write(
+ sprintf('Message ID: %s, Message Body: %s', $sms->sid, $sms->body)
+ );
+ return $response;
});
# [END gae_flex_twilio_send_sms]
@@ -58,17 +75,16 @@
/***
* Receive an sms.
*/
-$app->post('/sms/receive', function (Request $request) use ($app) {
- $sender = $request->get('From');
- $body = $request->get('Body');
+$app->post('/sms/receive', function (Request $request, Response $response) {
+ parse_str((string) $request->getBody(), $postData);
+ $sender = $postData['From'] ?? '';
+ $body = $postData['Body'] ?? '';
$message = "Hello, $sender, you said: $body";
- $response = new Services_Twilio_Twiml();
- $response->message($message);
- return new Response(
- (string) $response,
- 200,
- ['Content-Type' => 'application/xml']
- );
+ $twiml = new MessagingResponse();
+ $twiml->message($message);
+ $response->getBody()->write((string) $twiml);
+ return $response
+ ->withHeader('Content-Type', 'application/xml');
});
# [END gae_flex_twilio_receive_sms]
diff --git a/appengine/flexible/twilio/composer.json b/appengine/flexible/twilio/composer.json
index f45418b555..078df6f1d1 100644
--- a/appengine/flexible/twilio/composer.json
+++ b/appengine/flexible/twilio/composer.json
@@ -1,10 +1,7 @@
{
"require": {
- "silex/silex": "^2.3",
- "twilio/sdk": "^4.10"
- },
- "require-dev": {
- "paragonie/random_compat": "^2.0",
- "symfony/browser-kit": "^3.0"
+ "slim/slim": "^4.0",
+ "slim/psr7": "^1.3",
+ "twilio/sdk": "^6.18"
}
}
diff --git a/appengine/flexible/twilio/index.php b/appengine/flexible/twilio/index.php
index c764c29d51..e0ae9b5dfb 100644
--- a/appengine/flexible/twilio/index.php
+++ b/appengine/flexible/twilio/index.php
@@ -21,11 +21,6 @@
$app = require __DIR__ . '/app.php';
-$app['twilio.account_sid'] = getenv('TWILIO_ACCOUNT_SID');
-$app['twilio.auth_token'] = getenv('TWILIO_AUTH_TOKEN');
-$app['twilio.number'] = getenv('TWILIO_FROM_NUMBER');
-
// Run the app!
// use "gcloud app deploy" or run "php -S localhost:8000"
-$app['debug'] = true;
$app->run();
diff --git a/appengine/flexible/twilio/test/LocalTest.php b/appengine/flexible/twilio/test/LocalTest.php
index 15aefaa629..9269a417fa 100644
--- a/appengine/flexible/twilio/test/LocalTest.php
+++ b/appengine/flexible/twilio/test/LocalTest.php
@@ -14,73 +14,60 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Google\Cloud\TestUtils\TestTrait;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function createApplication()
- {
- $app = require __DIR__ . '/../app.php';
-
- // set some parameters for testing
- $app['session.test'] = true;
- $app['debug'] = true;
- $projectId = getenv('GOOGLE_PROJECT_ID');
-
- // set your Mailjet API key and secret
- $app['twilio.account_sid'] = getenv('TWILIO_ACCOUNT_SID');
- $app['twilio.auth_token'] = getenv('TWILIO_AUTH_TOKEN');
- $app['twilio.number'] = getenv('TWILIO_FROM_NUMBER');
+ use TestTrait;
- if (empty($app['twilio.account_sid']) ||
- empty($app['twilio.auth_token'])) {
- $this->markTestSkipped(
- 'set the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN ' .
- 'environment variables');
- }
+ private static $app;
- // prevent HTML error exceptions
- unset($app['exception_handler']);
+ public static function setUpBeforeClass(): void
+ {
+ self::$app = require __DIR__ . '/../app.php';
- return $app;
+ // set your Twilio API key and secret
+ self::requireEnv('TWILIO_ACCOUNT_SID');
+ self::requireEnv('TWILIO_AUTH_TOKEN');
}
public function testReceiveCall()
{
- $client = $this->createClient();
-
- $crawler = $client->request('POST', '/call/receive');
- $response = $client->getResponse();
+ $request = (new RequestFactory)->createRequest('POST', '/call/receive');
+ $response = self::$app->handle($request);
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString(
'Hello from Twilio!',
- $response->getContent()
+ (string) $response->getBody()
);
}
public function testReceiveSms()
{
- $client = $this->createClient();
$params = [
'From' => '16505551212',
'Body' => 'This is the best text message ever sent.'
];
- $crawler = $client->request('POST', '/sms/receive', $params);
- $response = $client->getResponse();
+ $request = (new RequestFactory)->createRequest('POST', '/sms/receive');
+ $request->getBody()->write(http_build_query($params));
+ $response = self::$app->handle($request);
+
$this->assertEquals(200, $response->getStatusCode());
- $this->assertStringContainsString($params['From'], $response->getContent());
- $this->assertStringContainsString($params['Body'], $response->getContent());
+ $this->assertStringContainsString($params['From'], $response->getBody());
+ $this->assertStringContainsString($params['Body'], $response->getBody());
}
public function testSendSms()
{
- $client = $this->createClient();
$params = [
'to' => '16505551212',
];
- $crawler = $client->request('POST', '/sms/send', $params);
- $response = $client->getResponse();
+ $request = (new RequestFactory)->createRequest('POST', '/sms/send');
+ $request->getBody()->write(http_build_query($params));
+ $response = self::$app->handle($request);
$this->assertEquals(200, $response->getStatusCode());
- $this->assertStringContainsString('Hello from Twilio!', $response->getContent());
+ $this->assertStringContainsString('Hello from Twilio!', $response->getBody());
}
}
diff --git a/appengine/flexible/websockets/README.md b/appengine/flexible/websockets/README.md
index 2b9df74fe0..1a09573656 100644
--- a/appengine/flexible/websockets/README.md
+++ b/appengine/flexible/websockets/README.md
@@ -7,14 +7,16 @@ This sample demonstrates how to use websockets on [Google App Engine Flexible En
Use the following commands to run locally:
```sh
- cd php-docs-samples/appengine/flexible/cloudsql
+ cd php-docs-samples/appengine/flexible/websockets
php -S localhost:8080
```
## Deploying
Refer to the [top-level README](../README.md) for instructions on running and deploying.
-Note that you will have to [create a firewall rule](https://cloud.google.com/sdk/gcloud/reference/compute/firewall-rules/create) that accepts traffic on port `8000`:
+**Important**: After you deploy to App Engine Flexible, you will need to request the page `/index.html` directly to access the sample (for example, `https://YOUR_VERSION-dot-YOUR_PROJECT_ID.uc.r.appspot.com/index.html`).
+
+You will also have to [create a firewall rule](https://cloud.google.com/sdk/gcloud/reference/compute/firewall-rules/create) that accepts traffic on port `8000`:
```sh
gcloud compute firewall-rules create allow-8000 --allow=tcp:8000 --target-tags=websockets-allow-8000
diff --git a/appengine/flexible/websockets/composer.json b/appengine/flexible/websockets/composer.json
index 81ec06da01..4e2b7a30ba 100644
--- a/appengine/flexible/websockets/composer.json
+++ b/appengine/flexible/websockets/composer.json
@@ -1,14 +1,10 @@
{
"require": {
- "cboden/ratchet": "^0.4.1",
- "guzzlehttp/psr7": "^1.5",
- "silex/silex": "^2.3"
+ "cboden/ratchet": "^0.4.1"
},
"require-dev": {
- "phpunit/phpunit": "^5",
"ratchet/pawl": "^0.3.4",
"react/promise": "^2.7",
- "clue/block-react": "^1.3",
- "google/cloud-tools": "^0.9.1"
+ "clue/block-react": "^1.3"
}
}
diff --git a/appengine/flexible/websockets/index.php b/appengine/flexible/websockets/index.php
deleted file mode 100644
index 93e49531aa..0000000000
--- a/appengine/flexible/websockets/index.php
+++ /dev/null
@@ -1,44 +0,0 @@
-register(new TwigServiceProvider(), [
- 'twig.path' => __DIR__
-]);
-
-$app->register(new Silex\Provider\RoutingServiceProvider());
-
-$app->get('/', function () use ($app) {
- return file_get_contents('index.html');
-});
-
-// @codeCoverageIgnoreStart
-if (PHP_SAPI != 'cli') {
- $app->run();
-}
-// @codeCoverageIgnoreEnd
-
-return $app;
diff --git a/appengine/flexible/websockets/test/system_test.php b/appengine/flexible/websockets/test/LocalTest.php
similarity index 77%
rename from appengine/flexible/websockets/test/system_test.php
rename to appengine/flexible/websockets/test/LocalTest.php
index 006efa81a3..c0e4e68717 100644
--- a/appengine/flexible/websockets/test/system_test.php
+++ b/appengine/flexible/websockets/test/LocalTest.php
@@ -16,29 +16,33 @@
*/
namespace Google\Cloud\Test;
+use Google\Cloud\TestUtils\TestTrait;
use PHPUnit\Framework\TestCase;
-use \Ratchet\Client;
-use \React\Promise\Promise;
-use \React\EventLoop;
-use \Clue\React\Block;
-
-require __DIR__ . '/../vendor/autoload.php';
+use Ratchet\Client\Connector;
+use React\Promise\Promise;
+use React\EventLoop\Factory;
+use Clue\React\Block;
class LocalTest extends TestCase
{
+ use TestTrait;
+
protected $loop;
protected function setUp(): void
{
- $this->loop = EventLoop\Factory::create();
+ $this->loop = Factory::create();
parent::setUp();
}
public function testIndex()
{
- $connector = new Client\Connector($this->loop);
- $basePromise = $connector('ws://' . getenv('GCLOUD_PROJECT') . '.appspot.com/ws')
+ $connector = new Connector($this->loop);
+ // The version of the deployed app
+ $version = $this->requireEnv('GOOGLE_VERSION_ID');
+ $url = sprintf('ws://%s-dot-%s.appspot.com/ws', $version, self::$projectId);
+ $basePromise = $connector($url)
->then(function ($conn) {
$endPromise = new Promise(function ($resolve) use ($conn) {
$conn->on('message', function ($msg) use ($resolve, $conn) {
diff --git a/appengine/standard/getting-started/composer.json b/appengine/standard/getting-started/composer.json
index 8af506846f..0dd488b1b2 100644
--- a/appengine/standard/getting-started/composer.json
+++ b/appengine/standard/getting-started/composer.json
@@ -2,15 +2,13 @@
"require": {
"google/cloud-storage": "^1.6",
"slim/slim": "^4.0",
+ "slim/psr7": "^1.0",
"slim/twig-view": "^3.0",
- "slim/http": "^1.0",
- "slim/psr7": "^1.0"
+ "php-di/slim-bridge": "^3.1"
},
"autoload": {
"psr-4": {
"Google\\Cloud\\Samples\\AppEngine\\GettingStarted\\": "src"
}
- },
- "require-dev": {
}
}
diff --git a/appengine/standard/getting-started/src/app.php b/appengine/standard/getting-started/src/app.php
index 2ee92c2aa6..9864d8013f 100644
--- a/appengine/standard/getting-started/src/app.php
+++ b/appengine/standard/getting-started/src/app.php
@@ -17,28 +17,31 @@
*/
/**
- * Create a new Silex Application with Twig. Configure it for debugging.
- * Follows Silex Skeleton pattern.
+ * Create a new Slim PHP Application with Twig. Configure it for debugging.
*/
+use DI\Container;
use Google\Cloud\Samples\AppEngine\GettingStarted\CloudSqlDataModel;
// [START gae_php_app_storage_client_import]
use Google\Cloud\Storage\StorageClient;
// [END gae_php_app_storage_client_import]
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
-$app = Slim\Factory\AppFactory::create();
-$app->addErrorMiddleware(true, true, true);
+AppFactory::setContainer($container = new Container());
+
+// Enable twig rendering
+$container->set('view', function () {
+ return Twig::create(__DIR__ . '/../templates');
+});
-// Get container
-$container = $app->getContainer();
+$app = AppFactory::create();
-// Register Twig
-$container['view'] = function ($container) {
- return new Slim\Views\Twig(__DIR__ . '/../templates');
-};
+// Display errors
+$app->addErrorMiddleware(true, true, true);
// Cloud Storage bucket
-$container['bucket'] = function ($container) {
+$container->set('bucket', function ($container) {
$bucketName = getenv('GOOGLE_STORAGE_BUCKET');
// [START gae_php_app_storage_client_setup]
// Your Google Cloud Storage bucket name and Project ID can be configured
@@ -51,10 +54,10 @@
$bucket = $storage->bucket($bucketName);
// [END gae_php_app_storage_client_setup]
return $bucket;
-};
+});
// Get the Cloud SQL MySQL connection object
-$container['cloudsql'] = function ($container) {
+$container->set('cloudsql', function ($container) {
// Data Model
$dbName = getenv('CLOUDSQL_DATABASE_NAME') ?: 'bookshelf';
$dbConn = getenv('CLOUDSQL_CONNECTION_NAME');
@@ -71,6 +74,6 @@
// [END gae_php_app_cloudsql_client_setup]
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return new CloudSqlDataModel($pdo);
-};
+});
return $app;
diff --git a/appengine/standard/getting-started/src/controllers.php b/appengine/standard/getting-started/src/controllers.php
index 4bedb8a7d2..c17ae9e9c0 100644
--- a/appengine/standard/getting-started/src/controllers.php
+++ b/appengine/standard/getting-started/src/controllers.php
@@ -19,76 +19,78 @@
namespace Google\Cloud\Samples\Bookshelf;
/*
- * Adds all the controllers to $app. Follows Silex Skeleton pattern.
+ * Adds all the controllers to Slim PHP $app.
*/
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Google\Cloud\Storage\Bucket;
-$app->get('/', function (Request $request, Response $response) {
+$container = $app->getContainer();
+
+$app->get('/', function (Request $request, Response $response) use ($container) {
return $response
->withHeader('Location', '/books')
->withStatus(302);
})->setName('home');
-$app->get('/books', function (Request $request, Response $response) {
- $token = (int) $request->getQueryParam('page_token');
- $bookList = $this->cloudsql->listBooks(10, $token);
+$app->get('/books', function (Request $request, Response $response) use ($container) {
+ $token = (int) $request->getUri()->getQuery('page_token');
+ $bookList = $container->get('cloudsql')->listBooks(10, $token);
- return $this->view->render($response, 'list.html.twig', [
+ return $container->get('view')->render($response, 'list.html.twig', [
'books' => $bookList['books'],
'next_page_token' => $bookList['cursor'],
]);
})->setName('books');
-$app->get('/books/add', function (Request $request, Response $response) {
- return $this->view->render($response, 'form.html.twig', [
+$app->get('/books/add', function (Request $request, Response $response) use ($container) {
+ return $container->get('view')->render($response, 'form.html.twig', [
'action' => 'Add',
'book' => array(),
]);
});
-$app->post('/books/add', function (Request $request, Response $response) {
+$app->post('/books/add', function (Request $request, Response $response) use ($container) {
$book = $request->getParsedBody();
$files = $request->getUploadedFiles();
if ($files['image']->getSize()) {
// Store the uploaded files in a Cloud Storage object.
$image = $files['image'];
- $object = $this->bucket->upload($image->getStream(), [
+ $object = $container->get('bucket')->upload($image->getStream(), [
'metadata' => ['contentType' => $image->getClientMediaType()],
'predefinedAcl' => 'publicRead',
]);
$book['image_url'] = $object->info()['mediaLink'];
}
- $id = $this->cloudsql->create($book);
+ $id = $container->get('cloudsql')->create($book);
return $response
->withHeader('Location', "/books/$id")
->withStatus(302);
});
-$app->get('/books/{id}', function (Request $request, Response $response, $args) {
- $book = $this->cloudsql->read($args['id']);
+$app->get('/books/{id}', function (Request $request, Response $response, $args) use ($container) {
+ $book = $container->get('cloudsql')->read($args['id']);
if (!$book) {
return $response->withStatus(404);
}
- return $this->view->render($response, 'view.html.twig', ['book' => $book]);
+ return $container->get('view')->render($response, 'view.html.twig', ['book' => $book]);
});
-$app->get('/books/{id}/edit', function (Request $request, Response $response, $args) {
- $book = $this->cloudsql->read($args['id']);
+$app->get('/books/{id}/edit', function (Request $request, Response $response, $args) use ($container) {
+ $book = $container->get('cloudsql')->read($args['id']);
if (!$book) {
return $response->withStatus(404);
}
- return $this->view->render($response, 'form.html.twig', [
+ return $container->get('view')->render($response, 'form.html.twig', [
'action' => 'Edit',
'book' => $book,
]);
});
-$app->post('/books/{id}/edit', function (Request $request, Response $response, $args) {
- if (!$this->cloudsql->read($args['id'])) {
+$app->post('/books/{id}/edit', function (Request $request, Response $response, $args) use ($container) {
+ if (!$container->get('cloudsql')->read($args['id'])) {
return $response->withStatus(404);
}
$book = $request->getParsedBody();
@@ -96,7 +98,7 @@
$files = $request->getUploadedFiles();
if ($files['image']->getSize()) {
$image = $files['image'];
- $bucket = $this->bucket;
+ $bucket = $container->get('bucket');
$imageStream = $image->getStream();
$imageContentType = $image->getClientMediaType();
// [START gae_php_app_upload_image]
@@ -112,22 +114,23 @@
// [END gae_php_app_upload_image]
$book['image_url'] = $imageUrl;
}
- if ($this->cloudsql->update($book)) {
+ if ($container->get('cloudsql')->update($book)) {
return $response
->withHeader('Location', "/books/$args[id]")
->withStatus(302);
}
- return new Response('Could not update book');
+ $response->getBody()->write('Could not update book');
+ return $response;
});
-$app->post('/books/{id}/delete', function (Request $request, Response $response, $args) {
- $book = $this->cloudsql->read($args['id']);
+$app->post('/books/{id}/delete', function (Request $request, Response $response, $args) use ($container) {
+ $book = $container->get('cloudsql')->read($args['id']);
if ($book) {
- $this->cloudsql->delete($args['id']);
+ $container->get('cloudsql')->delete($args['id']);
if (!empty($book['image_url'])) {
$objectName = parse_url(https://github.com/basename($book['image_url']), PHP_URL_PATH);
- $bucket = $this->bucket;
+ $bucket = $container->get('bucket');
// get bucket name from image
// [START gae_php_app_delete_image]
$object = $bucket->object($objectName);
diff --git a/appengine/standard/getting-started/test/ControllersTest.php b/appengine/standard/getting-started/test/ControllersTest.php
index 3690fabe16..65d72c414e 100644
--- a/appengine/standard/getting-started/test/ControllersTest.php
+++ b/appengine/standard/getting-started/test/ControllersTest.php
@@ -39,16 +39,15 @@ public function setUp(): void
// Mock CloudSql dependency
$container = $app->getContainer();
- $container['cloudsql'] = $this->createMock(CloudSqlDataModel::class);
+ $container->set('cloud_sql', $this->createMock(CloudSqlDataModel::class));
$this->app = $app;
}
public function testRoot()
{
- $action = $this->getAction('home');
- $request = (new RequestFactory)->createRequest('get', '/');
- $response = $action($request, new Response());
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = $this->app->handle($request);
$this->assertEquals(302, $response->getStatusCode());
}
@@ -200,12 +199,4 @@ public function testRoot()
// $client->submit($submitButton->form());
// $this->assertEquals(404, $client->getResponse()->getStatusCode());
// }
-
- private function getAction($name)
- {
- $route = $this->app->getRouteCollector()
- ->getNamedRoute($name);
-
- return $route->getCallable();
- }
}
diff --git a/cloud_sql/mysql/pdo/tests/IntegrationTest.php b/cloud_sql/mysql/pdo/tests/IntegrationTest.php
index ce7754496e..e7e37a8850 100644
--- a/cloud_sql/mysql/pdo/tests/IntegrationTest.php
+++ b/cloud_sql/mysql/pdo/tests/IntegrationTest.php
@@ -53,7 +53,6 @@ public function testUnixConnection()
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
-
$dbPass = $this->requireEnv('MYSQL_PASSWORD');
$dbName = $this->requireEnv('MYSQL_DATABASE');
$dbUser = $this->requireEnv('MYSQL_USER');
@@ -66,7 +65,7 @@ public function testUnixConnection()
$dbName,
$connectionName,
$socketDir,
- $conn_config,
+ $conn_config
));
$this->assertIsArray($votes->listVotes());
}
diff --git a/cloud_sql/postgres/pdo/tests/IntegrationTest.php b/cloud_sql/postgres/pdo/tests/IntegrationTest.php
index 4a17f929d5..906334e007 100644
--- a/cloud_sql/postgres/pdo/tests/IntegrationTest.php
+++ b/cloud_sql/postgres/pdo/tests/IntegrationTest.php
@@ -65,7 +65,7 @@ public function testUnixConnection()
$dbName,
$connectionName,
$socketDir,
- $connConfig,
+ $connConfig
));
$this->assertIsArray($votes->listVotes());
}
diff --git a/debugger/README.md b/debugger/README.md
index b4826ae452..d40cc00444 100644
--- a/debugger/README.md
+++ b/debugger/README.md
@@ -2,7 +2,7 @@
## Description
-This simple [Silex][silex] application demonstrates how to
+This simple [Slim][slim] application demonstrates how to
install and run the [Stackdriver Debugger Agent][debugger] for PHP.
[debugger]: https://cloud.google.com/debugger/docs/setup/php
@@ -47,7 +47,7 @@ for more information.
* See [LICENSE][license]
-[silex]: https://silex.symfony.com/
+[slim]: https://www.slimframework.com/
[pecl]: https://pecl.php.net/
[debug-console]: https://console.cloud.google.com/debug
[select-source-code]: https://cloud.google.com/debugger/docs/source-options]
diff --git a/debugger/composer.json b/debugger/composer.json
index 12cf563088..f4f5c3e78b 100644
--- a/debugger/composer.json
+++ b/debugger/composer.json
@@ -1,10 +1,8 @@
{
- "name": "google/debugger-sample",
- "type": "project",
"require": {
- "php": ">= 7.0",
"google/cloud-debugger": "^1.0.0",
- "silex/silex": "~2.0",
- "twig/twig": "^2.3"
+ "slim/slim": "^4.7",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.2"
}
}
diff --git a/debugger/views/hello.html.twig b/debugger/views/hello.html.twig
index 0be0ea75b0..f1c9009682 100644
--- a/debugger/views/hello.html.twig
+++ b/debugger/views/hello.html.twig
@@ -1,9 +1,9 @@
- Hello from Silex {{ constant('Silex\\Application::VERSION') }}
+ Hello from Slim {{ constant('Slim\\App::VERSION') }}
- Hello {{ name }} from Silex {{ constant('Silex\\Application::VERSION') }}
+ Hello {{ name }} from Slim {{ constant('Slim\\App::VERSION') }}
diff --git a/debugger/web/index.php b/debugger/web/index.php
index e7999f6475..a07ea273bd 100644
--- a/debugger/web/index.php
+++ b/debugger/web/index.php
@@ -21,19 +21,30 @@
$agent = new Agent(['sourceRoot' => realpath('../')]);
# [END debugger_agent]
-$app = new Silex\Application();
-$app->register(new Silex\Provider\TwigServiceProvider(), array(
- 'twig.path' => __DIR__ . '/../views',
-));
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
+use Slim\Views\TwigMiddleware;
-$app->get('/', function () {
- return 'Silex version ' . Silex\Application::VERSION;
+// Create App
+$app = AppFactory::create();
+
+// Create Twig
+$twig = Twig::create(__DIR__ . '/../views');
+
+// Add Twig-View Middleware
+$app->add(TwigMiddleware::create($app, $twig));
+
+$app->get('/', function (Request $request, Response $response) {
+ $response->getBody()->write('Slim version: ' . Slim\App::VERSION);
+ return $response;
});
-$app->get('/hello/{name}', function ($name) use ($app) {
- return $app['twig']->render('hello.html.twig', [
- 'name' => $name
+$app->get('/hello/{name}', function (Request $request, Response $response, $args) use ($twig) {
+ return $twig->render($response, 'hello.html.twig', [
+ 'name' => $args['name']
]);
});
diff --git a/dlp/quickstart.php b/dlp/quickstart.php
index c8e9bb6de3..15d793b995 100644
--- a/dlp/quickstart.php
+++ b/dlp/quickstart.php
@@ -62,7 +62,8 @@
$content = (new ContentItem())
->setValue($stringToInspect);
-$parent = $dlp->projectName(getEnv('GOOGLE_PROJECT_ID'));
+$projectId = getenv('GCLOUD_PROJECT');
+$parent = $dlp->projectName($projectId);
// Run request
$response = $dlp->inspectContent([
diff --git a/endpoints/getting-started/app.php b/endpoints/getting-started/app.php
index 633bd65e3a..66f95871f2 100644
--- a/endpoints/getting-started/app.php
+++ b/endpoints/getting-started/app.php
@@ -22,44 +22,60 @@
* various authentication methods.
*/
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Factory\AppFactory;
-// create the Silex application
-$app = new Application();
+// Create App
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$app->get('/', function (Request $request, Response $response) {
// Simple echo service.
$url = 'https://github.com/GoogleCloudPlatform/php-docs-samples/blob/master/endpoints/getting-started/README.md';
- $welcome = sprintf(
+ $response->getBody()->write(sprintf(
'Welcome to the Endpoints getting started tutorial!
' .
'Please see the README for instructions
',
$url
- );
- return $welcome;
+ ));
+
+ return $response;
});
-$app->post('/echo', function (Request $request) use ($app) {
+$app->post('/echo', function (Request $request, Response $response) use ($app) {
// Simple echo service.
- $message = $request->get('message');
- return $app->json(['message' => $message]);
+ $json = json_decode((string) $request->getBody(), true);
+ $response->getBody()->write(json_encode([
+ 'message' => $json['message'] ?? '',
+ ]));
+ return $response
+ ->withHeader('Content-Type', 'application/json');
});
-$app->get('/auth/info/googlejwt', function () use ($app) {
+$app->get('/auth/info/googlejwt', function (Request $request, Response $response) {
// Auth info with Google signed JWT.
- return $app->json($app['auth_info']);
+ $userInfo = get_user_info($request);
+ $response->getBody()->write(json_encode($userInfo));
+ return $response
+ ->withHeader('Content-Type', 'application/json');
});
-$app->get('/auth/info/googleidtoken', function () use ($app) {
+$app->get('/auth/info/googleidtoken', function (Request $request, Response $response) {
// Auth info with Google ID token.
- return $app->json($app['auth_info']);
+ $userInfo = get_user_info($request);
+ $response->getBody()->write(json_encode($userInfo));
+ return $response
+ ->withHeader('Content-Type', 'application/json');
});
-$app['auth_info'] = function (Request $request) use ($app) {
+function get_user_info(Request $request)
+{
// Retrieves the authenication information from Google Cloud Endpoints.
- $encoded_info = $request->headers->get('X-Endpoint-API-UserInfo');
+ $encoded_info = $request->getHeaderLine('X-Endpoint-API-UserInfo');
if ($encoded_info) {
$info_json = utf8_decode(base64_decode($encoded_info));
@@ -69,14 +85,6 @@
}
return $user_info;
-};
-
-// Accept JSON requests
-$app->before(function (Request $request) {
- if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
- $data = json_decode($request->getContent(), true);
- $request->request->replace(is_array($data) ? $data : array());
- }
-});
+}
return $app;
diff --git a/endpoints/getting-started/composer.json b/endpoints/getting-started/composer.json
index 2c8a7a1363..a65f3d9490 100644
--- a/endpoints/getting-started/composer.json
+++ b/endpoints/getting-started/composer.json
@@ -1,13 +1,10 @@
{
"require": {
- "silex/silex": " ^2.3",
+ "slim/slim": "^4.7",
+ "slim/psr7": "^1.3",
"symfony/console": " ^3.0",
"google/auth": "^1.8.0"
},
- "require-dev": {
- "symfony/browser-kit": " ^3.0",
- "paragonie/random_compat": " ^9.0.0"
- },
"autoload": {
"psr-4": {
"Google\\Cloud\\Samples\\Appengine\\Endpoints\\": ""
diff --git a/endpoints/getting-started/index.php b/endpoints/getting-started/index.php
index 94f61c3743..435131c044 100644
--- a/endpoints/getting-started/index.php
+++ b/endpoints/getting-started/index.php
@@ -24,5 +24,4 @@
// Run the app!
// use "gcloud app deploy" or run "php -S localhost:8080"
// and browse to "/"
-$app['debug'] = true;
$app->run();
diff --git a/endpoints/getting-started/test/LocalTest.php b/endpoints/getting-started/test/LocalTest.php
index 727f724b8e..ef67b0569b 100644
--- a/endpoints/getting-started/test/LocalTest.php
+++ b/endpoints/getting-started/test/LocalTest.php
@@ -14,24 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-use Silex\WebTestCase;
+use PHPUnit\Framework\TestCase;
+use Slim\Psr7\Factory\RequestFactory;
-class LocalTest extends WebTestCase
+class LocalTest extends TestCase
{
- public function createApplication()
- {
- $app = require __DIR__ . '/../app.php';
-
- // set some parameters for testing
- $app['session.test'] = true;
- $app['debug'] = true;
-
- return $app;
- }
-
public function testEcho()
{
- $client = $this->createClient();
+ $app = require __DIR__ . '/../app.php';
$message = <<request(
- 'POST',
- '/echo',
- [],
- [],
- [ 'CONTENT_TYPE' => 'application/json' ],
- json_encode([ 'message' => $message ])
- );
+ $request = (new RequestFactory)->createRequest('POST', '/echo')
+ ->withHeader('Content-Type', 'application/json');
+
+ $request->getBody()->write(json_encode([
+ 'message' => $message
+ ]));
- $response = $client->getResponse();
+ $response = $app->handle($request);
$this->assertEquals(200, $response->getStatusCode());
- $json = json_decode((string) $response->getContent(), true);
+ $json = json_decode((string) $response->getBody(), true);
$this->assertNotNull($json);
$this->assertArrayHasKey('message', $json);
$this->assertEquals($message, $json['message']);
diff --git a/pubsub/app/README.md b/pubsub/app/README.md
index 024a62ac20..bd5fd02c14 100644
--- a/pubsub/app/README.md
+++ b/pubsub/app/README.md
@@ -41,7 +41,7 @@ $ composer install
- Select "Generate new JSON key", then download a new JSON file.
- Set the following environment variable:
- `GOOGLE_APPLICATION_CREDENTIALS`: the file path to the downloaded JSON file.
- - `GOOGLE_PROJECT_ID`: your project ID.
+ - `GCLOUD_PROJECT`: your project ID.
Run the PHP build-in web server with the following command:
diff --git a/pubsub/app/app.php b/pubsub/app/app.php
index 4a4db40622..8c1c489300 100644
--- a/pubsub/app/app.php
+++ b/pubsub/app/app.php
@@ -17,29 +17,37 @@
namespace Google\Cloud\Samples\PubSub;
-use Silex\Application;
-use Silex\Provider\TwigServiceProvider;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\Response;
+use DI\Container;
use Google\Cloud\PubSub\PubSubClient;
use Google\Cloud\Datastore\DatastoreClient;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Factory\AppFactory;
+use Slim\Views\Twig;
-$app = new Application();
-$app->register(new TwigServiceProvider());
-$app['twig.path'] = [ __DIR__ ];
+// Create Container
+AppFactory::setContainer($container = new Container());
+$container->set('view', function () {
+ return Twig::create(__DIR__);
+});
+
+// Create App
+$app = AppFactory::create();
-$app->get('/', function () use ($app) {
- return $app['twig']->render('pubsub.html.twig', [
- 'project_id' => $app['project_id'],
+// Display errors
+$app->addErrorMiddleware(true, true, true);
+
+$app->get('/', function (Request $request, Response $response, $args) use ($container) {
+ return $container->get('view')->render($response, 'pubsub.html.twig', [
+ 'project_id' => $container->get('project_id'),
]);
});
-$app->get('/fetch_messages', function () use ($app) {
+$app->get('/fetch_messages', function (Request $request, Response $response, $args) use ($container) {
// get PUSH pubsub messages
- $projectId = $app['project_id'];
- $subscriptionName = $app['subscription'];
- $datastore = $app['datastore'];
+ $projectId = $container->get('project_id');
+ $subscriptionName = $container->get('subscription');
+ $datastore = $container->get('datastore');
$query = $datastore->query()->kind('PubSubPushMessage');
$messages = [];
$pushKeys = [];
@@ -67,10 +75,11 @@
$subscription->acknowledgeBatch($pullMessages);
}
# [END gae_flex_pubsub_index]
- return new JsonResponse($messages);
+ $response->getBody()->write(json_encode($messages));
+ return $response;
});
-$app->post('/receive_message', function (Request $request) use ($app) {
+$app->post('/receive_message', function (Request $request, Response $response, $args) use ($container) {
// pull the message from the post body
$json = json_decode($request->getContent(), true);
if (
@@ -80,35 +89,35 @@
return new Response('', 400);
}
// store the push message in datastore
- $datastore = $app['datastore'];
+ $datastore = $container->get('datastore');
$message = $datastore->entity('PubSubPushMessage', [
'message' => $message
]);
$datastore->insert($message);
- return new Response();
+ return $response;
});
-$app->post('/send_message', function (Request $request) use ($app) {
- $projectId = $app['project_id'];
- $topicName = $app['topic'];
+$app->post('/send_message', function (Request $request, Response $response, $args) use ($container) {
+ $projectId = $container->get('project_id');
+ $topicName = $container->get('topic');
# [START gae_flex_pubsub_push]
- if ($message = $request->get('message')) {
+ if ($message = (string) $request->getBody()) {
// Publish the pubsub message to the topic
$pubsub = new PubSubClient([
'projectId' => $projectId,
]);
$topic = $pubsub->topic($topicName);
- $response = $topic->publish(['data' => $message]);
- return new Response('', 204);
+ $topic->publish(['data' => $message]);
+ return $response->withStatus(204);
}
# [END gae_flex_pubsub_push]
- return new Response('', 400);
+ return $response->withStatus(400);
});
-$app['datastore'] = function () use ($app) {
+$container->set('datastore', function () use ($container) {
return new DatastoreClient([
- 'projectId' => $app['project_id'],
+ 'projectId' => $container->get('project_id'),
]);
-};
+});
return $app;
diff --git a/pubsub/app/app.yaml b/pubsub/app/app.yaml
index 7a004b4920..fbddd1bf88 100644
--- a/pubsub/app/app.yaml
+++ b/pubsub/app/app.yaml
@@ -1,12 +1,6 @@
-runtime: php55
-api_version: 1
-threadsafe: true
+runtime: php74
handlers:
-- url: pubsub.js
- static_file: pubsub.js
-- url: /.*
- script: index.php
-
-env_variables:
- GOOGLE_PROJECT_ID: "YOUR_PROJECT_ID"
+- url: /pubsub\.js
+ static_files: pubsub.js
+ upload: pubsub\.js
diff --git a/pubsub/app/composer.json b/pubsub/app/composer.json
index fee327cffd..0e177aa5f6 100644
--- a/pubsub/app/composer.json
+++ b/pubsub/app/composer.json
@@ -1,14 +1,10 @@
{
"require": {
- "php": ">=5.4",
"google/cloud-pubsub": "^1.23.0",
"google/cloud-datastore": "^1.11.2",
- "silex/silex": "^2.3",
- "twig/twig": "^2.12",
- "symfony/twig-bridge": "^4.4"
- },
- "require-dev": {
- "paragonie/random_compat": "^9.0.0",
- "symfony/browser-kit": "~2.7"
+ "slim/slim": "^4.7",
+ "slim/psr7": "^1.3",
+ "slim/twig-view": "^3.0",
+ "php-di/slim-bridge": "^3.1"
}
}
diff --git a/pubsub/app/index.php b/pubsub/app/index.php
index 3e4e6cb7b9..d064595d7a 100644
--- a/pubsub/app/index.php
+++ b/pubsub/app/index.php
@@ -20,12 +20,12 @@
require_once __DIR__ . '/vendor/autoload.php';
$app = require_once __DIR__ . '/app.php';
+$container = $app->getContainer();
-$app['project_id'] = getenv('GOOGLE_PROJECT_ID') ?: getenv('GCLOUD_PROJECT');
+$container->set('project_id', getenv('GCLOUD_PROJECT'));
# [START gae_flex_pubsub_env]
-$app['topic'] = 'php-example-topic';
-$app['subscription'] = 'php-example-subscription';
+$container->set('topic', 'php-example-topic');
+$container->set('subscription', 'php-example-subscription');
# [END gae_flex_pubsub_env]
-$app['debug'] = true;
$app->run();
diff --git a/pubsub/app/test/appTest.php b/pubsub/app/test/appTest.php
index 73be6521a7..f3e4ab6754 100644
--- a/pubsub/app/test/appTest.php
+++ b/pubsub/app/test/appTest.php
@@ -17,69 +17,56 @@
namespace Google\Cloud\Samples\PubSub\Tests;
+use PHPUnit\Framework\TestCase;
use Google\Cloud\TestUtils\TestTrait;
-use Silex\WebTestCase;
-use Symfony\Component\HttpKernel\Client;
+use Slim\Psr7\Factory\RequestFactory;
-class appTest extends WebTestCase
+class appTest extends TestCase
{
use TestTrait;
- public function createApplication()
+ private static $app;
+
+ public static function setUpBeforeClass(): void
{
// pull the app and set parameters for testing
- $app = require __DIR__ . '/../app.php';
-
- $app['session.test'] = true;
- $app['debug'] = true;
- $app['project_id'] = self::$projectId;
- $app['topic'] = $this->requireEnv('GOOGLE_PUBSUB_TOPIC');
- $app['subscription'] = $this->requireEnv('GOOGLE_PUBSUB_SUBSCRIPTION');
+ self::$app = require __DIR__ . '/../app.php';
- // prevent HTML error exceptions
- unset($app['exception_handler']);
-
- return $app;
+ $container = self::$app->getContainer();
+ $container->set('project_id', self::$projectId);
+ $container->set('topic', self::requireEnv('GOOGLE_PUBSUB_TOPIC'));
+ $container->set('subscription', self::requireEnv('GOOGLE_PUBSUB_SUBSCRIPTION'));
}
public function testInitialPage()
{
- // create the application
- $client = $this->createClient();
-
// make the request
- $crawler = $client->request('GET', '/');
+ $request = (new RequestFactory)->createRequest('GET', '/');
+ $response = self::$app->handle($request);
// test the response
- $this->assertTrue($client->getResponse()->isOk());
+ $this->assertEquals(200, $response->getStatusCode());
}
public function testFetchMessages()
{
- // create the application
- $app = $this->createApplication();
- $client = new Client($app);
-
// make the request
- $crawler = $client->request('GET', '/fetch_messages');
+ $request = (new RequestFactory)->createRequest('GET', '/fetch_messages');
+ $response = self::$app->handle($request);
// test the response
- $response = $client->getResponse();
- $this->assertTrue($response->isOk());
- $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
- $this->assertTrue(is_array(json_decode($response->getContent())));
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertTrue(is_array(json_decode($response->getBody())));
}
public function testSendMessage()
- { // create the application
- $app = $this->createApplication();
- $client = new Client($app);
-
+ {
// make the request
- $crawler = $client->request('POST', '/send_message', ['message' => 'foo']);
+ $request = (new RequestFactory)->createRequest('POST', '/send_message');
+ $request->getBody()->write(http_build_query(['message' => 'foo']));
+ $response = self::$app->handle($request);
// test the response
- $response = $client->getResponse();
$this->assertEquals(204, $response->getStatusCode());
}
}
diff --git a/renovate.json b/renovate.json
index b36ac66c3a..b684f7d146 100644
--- a/renovate.json
+++ b/renovate.json
@@ -11,8 +11,5 @@
"phpunit/phpunit"
]
}],
- "ignorePaths": [
- "appengine/flexible"
- ],
"prConcurrentLimit": 5
}
diff --git a/testing/run_test_suite.sh b/testing/run_test_suite.sh
index d54b2696b1..dd24822d29 100755
--- a/testing/run_test_suite.sh
+++ b/testing/run_test_suite.sh
@@ -29,22 +29,6 @@ FLAKES=(
# Directories we do not want to run tests in, even if they exist
SKIP_TESTS=(
- # Silex tests which aren't compatible with phpunit 8
- # TODO: Move these tests off Silex
- appengine/flexible/helloworld
- appengine/flexible/analytics
- appengine/flexible/twilio
- appengine/flexible/datastore
- appengine/flexible/tasks
- appengine/flexible/storage
- appengine/flexible/memcache
- appengine/flexible/logging
- endpoints/getting-started
- pubsub/app
- # PubSub batch is currently broken on PHP 8.0
- # @see https://github.com/googleapis/google-cloud-php/issues/3749
- # @TODO remove this once the above issue is fixed
- pubsub/api
)
# tests to run with grpc.so disabled
diff --git a/trace/trace-sample.php b/trace/trace-sample.php
index 5a920581e5..1fab0b9b9f 100644
--- a/trace/trace-sample.php
+++ b/trace/trace-sample.php
@@ -24,9 +24,9 @@
# [END trace_setup_php_use_statement]
-$projectId = getenv('GOOGLE_PROJECT_ID');
+$projectId = getenv('GCLOUD_PROJECT');
if ($projectId === false) {
- die('Set GOOGLE_PROJECT_ID envvar');
+ die('Set GCLOUD_PROJECT envvar');
}
# [START trace_setup_php_exporter_setup]
From 6e1e07f6ef402da178c1424d5a8dcce2e7c55bca Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Wed, 31 Mar 2021 19:20:04 +0200
Subject: [PATCH 007/624] fix(deps): update dependency google/cloud-tools to
^0.12.0 (#1325)
[](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [google/cloud-tools](https://togithub.com/GoogleCloudPlatform/php-tools) | require | minor | `^0.9.1` -> `^0.12.0` |
---
### Release Notes
GoogleCloudPlatform/php-tools
### [`v0.12.0`](https://togithub.com/GoogleCloudPlatform/php-tools/releases/v0.12.0)
[Compare Source](https://togithub.com/GoogleCloudPlatform/php-tools/compare/v0.11.0...v0.12.0)
#### Features
- add support for Twig v3 ([#82](https://togithub.com/GoogleCloudPlatform/php-tools/issues/82))
### [`v0.11.0`](https://togithub.com/GoogleCloudPlatform/php-tools/releases/v0.11.0)
[Compare Source](https://togithub.com/GoogleCloudPlatform/php-tools/compare/v0.10.0...v0.11.0)
#### Fixes
- **Backwards-Breaking**: remove deprecated `app-engine-php` component from `gcloud` install ([#83](https://togithub.com/GoogleCloudPlatform/php-tools/issues/83))
### [`v0.10.0`](https://togithub.com/GoogleCloudPlatform/php-tools/releases/v0.10.0)
[Compare Source](https://togithub.com/GoogleCloudPlatform/php-tools/compare/v0.9.1...v0.10.0)
#### Features
- feat: support for Guzzle 7.x ([#80](https://togithub.com/GoogleCloudPlatform/php-tools/issues/80))
- feat: support for symfony/console v5 ([#79](https://togithub.com/GoogleCloudPlatform/php-tools/issues/79))
- feat: `CloudRun` gcloud wrapper ([#71](https://togithub.com/GoogleCloudPlatform/php-tools/issues/71))
- feat: `DeploymentTrait` ([#59](https://togithub.com/GoogleCloudPlatform/php-tools/issues/59))
- feat: install options for `install_test_deps.sh` script ([#67](https://togithub.com/GoogleCloudPlatform/php-tools/issues/67))
- feat: ability to combine backoffs ([#66](https://togithub.com/GoogleCloudPlatform/php-tools/issues/66))
- feat: flag in gcloud wrapper to allow for running `gcloud beta` ([#64](https://togithub.com/GoogleCloudPlatform/php-tools/issues/64))
- feat: `runSnippets` function to `TestTrait` ([#61](https://togithub.com/GoogleCloudPlatform/php-tools/issues/61))
#### Fixes
- fix: update to only use maintained versions of symfony ([#74](https://togithub.com/GoogleCloudPlatform/php-tools/issues/74))
- fix: only set cwd in ExecuteCommandTrait when it exists ([#78](https://togithub.com/GoogleCloudPlatform/php-tools/issues/78))
- fix: remove [@beforeClass](https://togithub.com/beforeClass) from AppEngineDeploymentTrait::deployApp ([#72](https://togithub.com/GoogleCloudPlatform/php-tools/issues/72))
- fix: renames .php_cs to .php_cs.dist ([#62](https://togithub.com/GoogleCloudPlatform/php-tools/issues/62))
- test: Add PHP 7.2 and 7.3 to travis testing ([#65](https://togithub.com/GoogleCloudPlatform/php-tools/issues/65))
---
### Renovate configuration
:date: **Schedule**: At any time (no schedule defined).
:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
:recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this PR, check this box
---
This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/GoogleCloudPlatform/php-docs-samples).
---
appengine/flexible/wordpress/composer.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/appengine/flexible/wordpress/composer.json b/appengine/flexible/wordpress/composer.json
index b16df131f8..e1dda04052 100644
--- a/appengine/flexible/wordpress/composer.json
+++ b/appengine/flexible/wordpress/composer.json
@@ -4,11 +4,10 @@
"ext-zip": "*",
"paragonie/random_compat": "^1.3",
"symfony/console": "^3.0",
- "google/cloud-tools": "^0.9.1"
+ "google/cloud-tools": "^0.12.0"
},
"require-dev": {
- "guzzlehttp/guzzle": "~6.0",
- "symfony/browser-kit": "~2"
+ "guzzlehttp/guzzle": "~6.0"
},
"autoload-dev": {
"psr-4": {
From f6d6b4594c6742d3cc1262d4bd0d45e9f85a59ec Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Wed, 31 Mar 2021 10:38:33 -0700
Subject: [PATCH 008/624] chore(tests): add deploy group (#1320)
---
appengine/standard/auth/test/DeployTest.php | 3 +++
appengine/standard/errorreporting/test/DeployTest.php | 4 +++-
appengine/standard/front-controller/test/DeployTest.php | 3 +++
appengine/standard/getting-started/test/DeployTest.php | 1 +
appengine/standard/grpc/test/DeployTest.php | 3 +++
appengine/standard/helloworld/test/DeployTest.php | 3 +++
.../standard/laravel-framework/test/DeployDatabaseTest.php | 3 +++
.../laravel-framework/test/DeployStackdriverTest.php | 3 +++
appengine/standard/laravel-framework/test/DeployTest.php | 3 +++
appengine/standard/logging/test/DeployTest.php | 4 +++-
appengine/standard/memorystore/test/DeployTest.php | 3 +++
appengine/standard/metadata/test/DeployTest.php | 3 +++
appengine/standard/slim-framework/test/DeployTest.php | 3 +++
appengine/standard/storage/test/DeployTest.php | 3 +++
.../standard/symfony-framework/test/DeployDoctrineTest.php | 3 +++
appengine/standard/symfony-framework/test/DeployTest.php | 3 +++
appengine/standard/tasks/apps/handler/test/DeployTest.php | 3 +++
appengine/standard/trace/test/DeployTest.php | 3 +++
appengine/standard/wordpress/test/DeployTest.php | 3 +++
endpoints/getting-started/test/DeployTest.php | 3 +++
eventarc/generic/test/DeployTest.php | 1 +
functions/concepts_filesystem/test/DeployTest.php | 5 +++--
functions/concepts_requests/test/DeployTest.php | 5 +++--
functions/env_vars/test/DeployTest.php | 5 +++--
functions/firebase_firestore/test/DeployTest.php | 1 +
functions/firebase_firestore_reactive/test/DeployTest.php | 1 +
functions/firebase_remote_config/test/DeployTest.php | 1 +
functions/firebase_rtdb/test/DeployTest.php | 3 ++-
functions/helloworld_get/test/DeployTest.php | 5 +++--
functions/helloworld_http/test/DeployTest.php | 5 +++--
functions/helloworld_log/test/DeployTest.php | 1 +
functions/helloworld_pubsub/test/DeployTest.php | 1 +
functions/helloworld_storage/test/DeployTest.php | 1 +
functions/http_content_type/test/DeployTest.php | 1 +
functions/http_cors/test/DeployTest.php | 1 +
functions/http_form_data/test/DeployTest.php | 1 +
functions/http_method/test/DeployTest.php | 5 +++--
functions/imagemagick/test/DeployTest.php | 5 +++--
functions/slack_slash_command/test/DeployTest.php | 5 +++--
functions/tips_infinite_retries/test/DeployTest.php | 2 +-
functions/tips_phpinfo/test/DeployTest.php | 1 +
functions/tips_retry/test/DeployTest.php | 2 +-
functions/tips_scopes/test/DeployTest.php | 1 +
pubsub/app/phpunit.xml.dist | 1 +
run/helloworld/test/DeployTest.php | 1 +
45 files changed, 100 insertions(+), 21 deletions(-)
diff --git a/appengine/standard/auth/test/DeployTest.php b/appengine/standard/auth/test/DeployTest.php
index c316aaa3d5..088eed0fa7 100644
--- a/appengine/standard/auth/test/DeployTest.php
+++ b/appengine/standard/auth/test/DeployTest.php
@@ -19,6 +19,9 @@
use Google\Cloud\TestUtils\AppEngineDeploymentTrait;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/errorreporting/test/DeployTest.php b/appengine/standard/errorreporting/test/DeployTest.php
index 49b41a7763..d40cee1efe 100644
--- a/appengine/standard/errorreporting/test/DeployTest.php
+++ b/appengine/standard/errorreporting/test/DeployTest.php
@@ -21,9 +21,11 @@
use Google\Cloud\ErrorReporting\V1beta1\ErrorStatsServiceClient;
use Google\Cloud\ErrorReporting\V1beta1\QueryTimeRange;
use Google\Cloud\ErrorReporting\V1beta1\QueryTimeRange_Period;
-
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/front-controller/test/DeployTest.php b/appengine/standard/front-controller/test/DeployTest.php
index 6c8812d217..91e4f5d9ff 100644
--- a/appengine/standard/front-controller/test/DeployTest.php
+++ b/appengine/standard/front-controller/test/DeployTest.php
@@ -20,6 +20,9 @@
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/getting-started/test/DeployTest.php b/appengine/standard/getting-started/test/DeployTest.php
index 76877e415d..6f68118e64 100644
--- a/appengine/standard/getting-started/test/DeployTest.php
+++ b/appengine/standard/getting-started/test/DeployTest.php
@@ -24,6 +24,7 @@
/**
* Class DeployTest
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/appengine/standard/grpc/test/DeployTest.php b/appengine/standard/grpc/test/DeployTest.php
index 3b69e1f3a3..10a6cc19e4 100644
--- a/appengine/standard/grpc/test/DeployTest.php
+++ b/appengine/standard/grpc/test/DeployTest.php
@@ -20,6 +20,9 @@
use Google\Cloud\TestUtils\FileUtil;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/helloworld/test/DeployTest.php b/appengine/standard/helloworld/test/DeployTest.php
index 7b96299297..a2ff8055da 100644
--- a/appengine/standard/helloworld/test/DeployTest.php
+++ b/appengine/standard/helloworld/test/DeployTest.php
@@ -19,6 +19,9 @@
use Google\Cloud\TestUtils\AppEngineDeploymentTrait;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/laravel-framework/test/DeployDatabaseTest.php b/appengine/standard/laravel-framework/test/DeployDatabaseTest.php
index 6d81702cb5..c300557696 100644
--- a/appengine/standard/laravel-framework/test/DeployDatabaseTest.php
+++ b/appengine/standard/laravel-framework/test/DeployDatabaseTest.php
@@ -23,6 +23,9 @@
require_once __DIR__ . '/DeployLaravelTrait.php';
+/**
+ * @group deploy
+ */
class DeployDatabaseTest extends TestCase
{
use TestTrait;
diff --git a/appengine/standard/laravel-framework/test/DeployStackdriverTest.php b/appengine/standard/laravel-framework/test/DeployStackdriverTest.php
index 7be2d4a369..3aa6c329a8 100644
--- a/appengine/standard/laravel-framework/test/DeployStackdriverTest.php
+++ b/appengine/standard/laravel-framework/test/DeployStackdriverTest.php
@@ -24,6 +24,9 @@
require_once __DIR__ . '/DeployLaravelTrait.php';
+/**
+ * @group deploy
+ */
class DeployStackdriverTest extends TestCase
{
use DeployLaravelTrait;
diff --git a/appengine/standard/laravel-framework/test/DeployTest.php b/appengine/standard/laravel-framework/test/DeployTest.php
index c60817ad21..75bfac75b2 100644
--- a/appengine/standard/laravel-framework/test/DeployTest.php
+++ b/appengine/standard/laravel-framework/test/DeployTest.php
@@ -22,6 +22,9 @@
require_once __DIR__ . '/DeployLaravelTrait.php';
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use DeployLaravelTrait;
diff --git a/appengine/standard/logging/test/DeployTest.php b/appengine/standard/logging/test/DeployTest.php
index de71132eb2..f1e083e56a 100644
--- a/appengine/standard/logging/test/DeployTest.php
+++ b/appengine/standard/logging/test/DeployTest.php
@@ -20,9 +20,11 @@
use Google\Cloud\TestUtils\AppEngineDeploymentTrait;
use Google\Cloud\TestUtils\EventuallyConsistentTestTrait;
use Google\Cloud\Logging\LoggingClient;
-
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use TestTrait;
diff --git a/appengine/standard/memorystore/test/DeployTest.php b/appengine/standard/memorystore/test/DeployTest.php
index 849f5ab60d..d08339c579 100644
--- a/appengine/standard/memorystore/test/DeployTest.php
+++ b/appengine/standard/memorystore/test/DeployTest.php
@@ -27,6 +27,9 @@ class DeployTest extends TestCase
use TestTrait;
use AppEngineDeploymentTrait;
+ /**
+ * @group deploy
+ */
public function testIndex()
{
$resp = $this->client->request('GET', '/');
diff --git a/appengine/standard/metadata/test/DeployTest.php b/appengine/standard/metadata/test/DeployTest.php
index 8402dd35e2..71ca308ff0 100644
--- a/appengine/standard/metadata/test/DeployTest.php
+++ b/appengine/standard/metadata/test/DeployTest.php
@@ -19,6 +19,9 @@
use Google\Cloud\TestUtils\AppEngineDeploymentTrait;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/slim-framework/test/DeployTest.php b/appengine/standard/slim-framework/test/DeployTest.php
index 122b4f0e97..80670b972a 100644
--- a/appengine/standard/slim-framework/test/DeployTest.php
+++ b/appengine/standard/slim-framework/test/DeployTest.php
@@ -19,6 +19,9 @@
use Google\Cloud\TestUtils\AppEngineDeploymentTrait;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/storage/test/DeployTest.php b/appengine/standard/storage/test/DeployTest.php
index adbcffbb48..cfeb1a6fc7 100644
--- a/appengine/standard/storage/test/DeployTest.php
+++ b/appengine/standard/storage/test/DeployTest.php
@@ -23,6 +23,9 @@
use Symfony\Component\Yaml\Yaml;
use GuzzleHttp\Client;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/symfony-framework/test/DeployDoctrineTest.php b/appengine/standard/symfony-framework/test/DeployDoctrineTest.php
index ab31d95f60..56017acbd5 100644
--- a/appengine/standard/symfony-framework/test/DeployDoctrineTest.php
+++ b/appengine/standard/symfony-framework/test/DeployDoctrineTest.php
@@ -21,6 +21,9 @@
require_once __DIR__ . '/DeploySymfonyTrait.php';
+/**
+ * @group deploy
+ */
class DeployDoctrineTest extends TestCase
{
use DeploySymfonyTrait;
diff --git a/appengine/standard/symfony-framework/test/DeployTest.php b/appengine/standard/symfony-framework/test/DeployTest.php
index cc4df8f3f0..f5c039e57d 100644
--- a/appengine/standard/symfony-framework/test/DeployTest.php
+++ b/appengine/standard/symfony-framework/test/DeployTest.php
@@ -23,6 +23,9 @@
require_once __DIR__ . '/DeploySymfonyTrait.php';
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use DeploySymfonyTrait;
diff --git a/appengine/standard/tasks/apps/handler/test/DeployTest.php b/appengine/standard/tasks/apps/handler/test/DeployTest.php
index 2cebf29522..7975444729 100644
--- a/appengine/standard/tasks/apps/handler/test/DeployTest.php
+++ b/appengine/standard/tasks/apps/handler/test/DeployTest.php
@@ -22,6 +22,9 @@
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use TestTrait;
diff --git a/appengine/standard/trace/test/DeployTest.php b/appengine/standard/trace/test/DeployTest.php
index 751a7e8a7f..78e62b8ae5 100644
--- a/appengine/standard/trace/test/DeployTest.php
+++ b/appengine/standard/trace/test/DeployTest.php
@@ -23,6 +23,9 @@
use GuzzleHttp\HandlerStack;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/appengine/standard/wordpress/test/DeployTest.php b/appengine/standard/wordpress/test/DeployTest.php
index ac34232d60..98750cecfe 100644
--- a/appengine/standard/wordpress/test/DeployTest.php
+++ b/appengine/standard/wordpress/test/DeployTest.php
@@ -21,6 +21,9 @@
use Google\Cloud\TestUtils\ExecuteCommandTrait;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use ExecuteCommandTrait;
diff --git a/endpoints/getting-started/test/DeployTest.php b/endpoints/getting-started/test/DeployTest.php
index 557e974dff..a339cc277a 100644
--- a/endpoints/getting-started/test/DeployTest.php
+++ b/endpoints/getting-started/test/DeployTest.php
@@ -20,6 +20,9 @@
use Google\Cloud\TestUtils\FileUtil;
use PHPUnit\Framework\TestCase;
+/**
+ * @group deploy
+ */
class DeployTest extends TestCase
{
use AppEngineDeploymentTrait;
diff --git a/eventarc/generic/test/DeployTest.php b/eventarc/generic/test/DeployTest.php
index b7e52c4d00..461c5b4e50 100644
--- a/eventarc/generic/test/DeployTest.php
+++ b/eventarc/generic/test/DeployTest.php
@@ -27,6 +27,7 @@
/**
* Class DeployTest.
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/concepts_filesystem/test/DeployTest.php b/functions/concepts_filesystem/test/DeployTest.php
index 2ab2a62c92..af2cf80074 100644
--- a/functions/concepts_filesystem/test/DeployTest.php
+++ b/functions/concepts_filesystem/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,8 +41,8 @@ class DeployTest extends TestCase
private static $entryPoint = 'listFiles';
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction($file): void
{
// Send a request to the function.
diff --git a/functions/concepts_requests/test/DeployTest.php b/functions/concepts_requests/test/DeployTest.php
index 1fe8de9913..a8521647ce 100644
--- a/functions/concepts_requests/test/DeployTest.php
+++ b/functions/concepts_requests/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -51,8 +52,8 @@ private static function doDeploy()
}
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction($statusCode): void
{
// Send a request to the function.
diff --git a/functions/env_vars/test/DeployTest.php b/functions/env_vars/test/DeployTest.php
index 7d361c3cee..fa046cd5d7 100644
--- a/functions/env_vars/test/DeployTest.php
+++ b/functions/env_vars/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -53,8 +54,8 @@ private static function doDeploy()
}
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction(
$statusCode,
$varName,
diff --git a/functions/firebase_firestore/test/DeployTest.php b/functions/firebase_firestore/test/DeployTest.php
index 515bcbf6ea..854f3b93e3 100644
--- a/functions/firebase_firestore/test/DeployTest.php
+++ b/functions/firebase_firestore/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/firebase_firestore_reactive/test/DeployTest.php b/functions/firebase_firestore_reactive/test/DeployTest.php
index 7123b230b5..825a36b500 100644
--- a/functions/firebase_firestore_reactive/test/DeployTest.php
+++ b/functions/firebase_firestore_reactive/test/DeployTest.php
@@ -32,6 +32,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/firebase_remote_config/test/DeployTest.php b/functions/firebase_remote_config/test/DeployTest.php
index aabc494598..f72ffc39ec 100644
--- a/functions/firebase_remote_config/test/DeployTest.php
+++ b/functions/firebase_remote_config/test/DeployTest.php
@@ -32,6 +32,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/firebase_rtdb/test/DeployTest.php b/functions/firebase_rtdb/test/DeployTest.php
index ad389b53d4..1936d3b6ab 100644
--- a/functions/firebase_rtdb/test/DeployTest.php
+++ b/functions/firebase_rtdb/test/DeployTest.php
@@ -32,6 +32,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,7 +41,7 @@ class DeployTest extends TestCase
/** @var string */
private static $entryPoint = 'firebaseRTDB';
-
+
/** @var string */
private static $functionSignatureType = 'cloudevent';
diff --git a/functions/helloworld_get/test/DeployTest.php b/functions/helloworld_get/test/DeployTest.php
index 5dfdd82d59..d4c94e3727 100644
--- a/functions/helloworld_get/test/DeployTest.php
+++ b/functions/helloworld_get/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,8 +41,8 @@ class DeployTest extends TestCase
private static $entryPoint = 'helloGet';
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction($statusCode, $expected): void
{
// Send a request to the function.
diff --git a/functions/helloworld_http/test/DeployTest.php b/functions/helloworld_http/test/DeployTest.php
index 22d7622836..aec6623ed1 100644
--- a/functions/helloworld_http/test/DeployTest.php
+++ b/functions/helloworld_http/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,8 +41,8 @@ class DeployTest extends TestCase
private static $entryPoint = 'helloHttp';
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction(
$label,
$query,
diff --git a/functions/helloworld_log/test/DeployTest.php b/functions/helloworld_log/test/DeployTest.php
index d171c2734b..0a6effbb95 100644
--- a/functions/helloworld_log/test/DeployTest.php
+++ b/functions/helloworld_log/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/helloworld_pubsub/test/DeployTest.php b/functions/helloworld_pubsub/test/DeployTest.php
index cbc95cb8af..fdea505e47 100644
--- a/functions/helloworld_pubsub/test/DeployTest.php
+++ b/functions/helloworld_pubsub/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/helloworld_storage/test/DeployTest.php b/functions/helloworld_storage/test/DeployTest.php
index 638eedda88..38eea6e7a3 100644
--- a/functions/helloworld_storage/test/DeployTest.php
+++ b/functions/helloworld_storage/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/http_content_type/test/DeployTest.php b/functions/http_content_type/test/DeployTest.php
index f69cded654..0c787005fc 100644
--- a/functions/http_content_type/test/DeployTest.php
+++ b/functions/http_content_type/test/DeployTest.php
@@ -29,6 +29,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/http_cors/test/DeployTest.php b/functions/http_cors/test/DeployTest.php
index a5bc0652fa..075fb0513f 100644
--- a/functions/http_cors/test/DeployTest.php
+++ b/functions/http_cors/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/http_form_data/test/DeployTest.php b/functions/http_form_data/test/DeployTest.php
index de627d75be..18d28d34c7 100644
--- a/functions/http_form_data/test/DeployTest.php
+++ b/functions/http_form_data/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/http_method/test/DeployTest.php b/functions/http_method/test/DeployTest.php
index 1b3f1cdc72..25d4d2df37 100644
--- a/functions/http_method/test/DeployTest.php
+++ b/functions/http_method/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,8 +41,8 @@ class DeployTest extends TestCase
private static $entryPoint = 'httpMethod';
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction(
$method,
$statusCode,
diff --git a/functions/imagemagick/test/DeployTest.php b/functions/imagemagick/test/DeployTest.php
index 3ee40d5b50..e92ab2a922 100644
--- a/functions/imagemagick/test/DeployTest.php
+++ b/functions/imagemagick/test/DeployTest.php
@@ -33,6 +33,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -65,8 +66,8 @@ class DeployTest extends TestCase
private static $loggingClient;
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction(
$cloudevent,
$label,
diff --git a/functions/slack_slash_command/test/DeployTest.php b/functions/slack_slash_command/test/DeployTest.php
index 7a97103c8a..56b4309230 100644
--- a/functions/slack_slash_command/test/DeployTest.php
+++ b/functions/slack_slash_command/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -40,8 +41,8 @@ class DeployTest extends TestCase
private static $entryPoint = 'receiveRequest';
/**
- * @dataProvider cases
- */
+ * @dataProvider cases
+ */
public function testFunction(
$label,
$body,
diff --git a/functions/tips_infinite_retries/test/DeployTest.php b/functions/tips_infinite_retries/test/DeployTest.php
index fc684bdb3e..b952321a67 100644
--- a/functions/tips_infinite_retries/test/DeployTest.php
+++ b/functions/tips_infinite_retries/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -47,7 +48,6 @@ class DeployTest extends TestCase
/** @var LoggingClient */
private static $loggingClient;
-
public function testTipsRetry(): void
{
// Send Pub/Sub message.
diff --git a/functions/tips_phpinfo/test/DeployTest.php b/functions/tips_phpinfo/test/DeployTest.php
index ca8e20f22d..ea2e47fb51 100644
--- a/functions/tips_phpinfo/test/DeployTest.php
+++ b/functions/tips_phpinfo/test/DeployTest.php
@@ -29,6 +29,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/functions/tips_retry/test/DeployTest.php b/functions/tips_retry/test/DeployTest.php
index 120ecc40ec..446f88e405 100644
--- a/functions/tips_retry/test/DeployTest.php
+++ b/functions/tips_retry/test/DeployTest.php
@@ -31,6 +31,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
@@ -47,7 +48,6 @@ class DeployTest extends TestCase
/** @var LoggingClient */
private static $loggingClient;
-
public function testTipsRetry(): void
{
// Send Pub/Sub message.
diff --git a/functions/tips_scopes/test/DeployTest.php b/functions/tips_scopes/test/DeployTest.php
index 0263b3796c..b17941e499 100644
--- a/functions/tips_scopes/test/DeployTest.php
+++ b/functions/tips_scopes/test/DeployTest.php
@@ -29,6 +29,7 @@
*
* To skip deployment of a new function, run with "GOOGLE_SKIP_DEPLOYMENT=true".
* To skip deletion of the tested function, run with "GOOGLE_KEEP_DEPLOYMENT=true".
+ * @group deploy
*/
class DeployTest extends TestCase
{
diff --git a/pubsub/app/phpunit.xml.dist b/pubsub/app/phpunit.xml.dist
index 8f27f1e016..96c2523e20 100644
--- a/pubsub/app/phpunit.xml.dist
+++ b/pubsub/app/phpunit.xml.dist
@@ -18,6 +18,7 @@
test
+ test/DeployAppEngineFlexTest.php
diff --git a/run/helloworld/test/DeployTest.php b/run/helloworld/test/DeployTest.php
index 623f3724ae..894bae7373 100644
--- a/run/helloworld/test/DeployTest.php
+++ b/run/helloworld/test/DeployTest.php
@@ -28,6 +28,7 @@
/**
* Class DeployTest.
+ * @group deploy
*/
class DeloyTest extends TestCase
{
From b8453c6f6d76bf94768078b025169510a7784d45 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Wed, 31 Mar 2021 20:55:52 +0200
Subject: [PATCH 009/624] fix(deps): update dependency
google/cloud-error-reporting to ^0.18.0 (#1324)
---
appengine/flexible/logging/composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/appengine/flexible/logging/composer.json b/appengine/flexible/logging/composer.json
index db518467f2..e3b309fe15 100644
--- a/appengine/flexible/logging/composer.json
+++ b/appengine/flexible/logging/composer.json
@@ -1,7 +1,7 @@
{
"require": {
"google/cloud-logging": "^1.21.0",
- "google/cloud-error-reporting": "^0.16.2",
+ "google/cloud-error-reporting": "^0.18.0",
"slim/slim": "^4.0",
"slim/psr7": "^1.3",
"slim/twig-view": "^3.2"
From 1c51e806ff4df2a0d13ce9c1a01248f1082e3dbc Mon Sep 17 00:00:00 2001
From: Ace Nassri
Date: Thu, 1 Apr 2021 22:28:02 -0700
Subject: [PATCH 010/624] chore(GAE logging): fix slow tests (#1330)
---
appengine/standard/logging/test/DeployTest.php | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/appengine/standard/logging/test/DeployTest.php b/appengine/standard/logging/test/DeployTest.php
index f1e083e56a..f707b1b156 100644
--- a/appengine/standard/logging/test/DeployTest.php
+++ b/appengine/standard/logging/test/DeployTest.php
@@ -41,17 +41,22 @@ public function testIndex()
$response->getBody()->getContents()
);
- $this->verifyLog('This will show up as log level INFO', 'info');
+ $this->verifyLog('This will show up as log level INFO', 'info', 3);
+
+ // These should succeed if the above call has too.
+ // Thus, they need fewer retries!
$this->verifyLog('This will show up as log level WARNING', 'warning');
$this->verifyLog('This will show up as log level ERROR', 'error');
}
- private function verifyLog($message, $level, $retryCount = 5)
+ private function verifyLog($message, $level, $retryCount = 2)
{
+ $fiveMinAgo = date(\DateTime::RFC3339, strtotime('-5 minutes'));
$filter = sprintf(
- 'resource.type = "gae_app" AND severity = "%s" AND logName = "%s"',
+ 'resource.type="gae_app" severity="%s" logName="%s" timestamp>="%s"',
strtoupper($level),
- sprintf('projects/%s/logs/app', self::$projectId)
+ sprintf('projects/%s/logs/app', self::$projectId),
+ $fiveMinAgo
);
$logOptions = [
'pageSize' => 20,
From e0bd549ce121924ce94f828cfce70775f0ee9dd7 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Fri, 2 Apr 2021 17:27:20 +0200
Subject: [PATCH 011/624] fix(deps): update dependency drush/drush to v10
(#1327)
---
appengine/flexible/drupal8/composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/appengine/flexible/drupal8/composer.json b/appengine/flexible/drupal8/composer.json
index b2f989f35b..3c23a29d55 100644
--- a/appengine/flexible/drupal8/composer.json
+++ b/appengine/flexible/drupal8/composer.json
@@ -1,6 +1,6 @@
{
"require": {
- "drush/drush": "^8.1"
+ "drush/drush": "^10.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.3",
From d1e77c9cbc987331ebcf4309a0fa89207f8a7520 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Fri, 2 Apr 2021 17:37:33 +0200
Subject: [PATCH 012/624] fix(deps): update dependency react/http to v1 (#1331)
---
appengine/flexible/supervisord/replacement/composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/appengine/flexible/supervisord/replacement/composer.json b/appengine/flexible/supervisord/replacement/composer.json
index ad2aec1b01..85463c984d 100644
--- a/appengine/flexible/supervisord/replacement/composer.json
+++ b/appengine/flexible/supervisord/replacement/composer.json
@@ -1,5 +1,5 @@
{
"require": {
- "react/http": "^0.8"
+ "react/http": "^1.0"
}
}
From 317ec924eea4e28d408b90b4bd17a524b7c3272f Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Fri, 2 Apr 2021 17:38:00 +0200
Subject: [PATCH 013/624] chore(deps): update dependency wp-cli/wp-cli to v2
(#1326)
---
appengine/flexible/wordpress/files/composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/appengine/flexible/wordpress/files/composer.json b/appengine/flexible/wordpress/files/composer.json
index 8564d59695..e2f6359b96 100644
--- a/appengine/flexible/wordpress/files/composer.json
+++ b/appengine/flexible/wordpress/files/composer.json
@@ -3,6 +3,6 @@
"google/cloud": "~0.21"
},
"require-dev": {
- "wp-cli/wp-cli": "~1.1"
+ "wp-cli/wp-cli": "~2.0"
}
}
From b20b338bb2161c7ee919ba46219686e153d316f0 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Fri, 2 Apr 2021 08:50:03 -0700
Subject: [PATCH 014/624] Update .kokoro/system_tests.sh
---
.kokoro/system_tests.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh
index 2a2c6a0183..fe27d64fe0 100755
--- a/.kokoro/system_tests.sh
+++ b/.kokoro/system_tests.sh
@@ -66,7 +66,7 @@ composer install -d testing/
# cd into specific subdirectory (if appropriate)
MAIN_DIR=$(pwd)
-if [[ -z "${SUB_DIRECTORY}" ]]; then
+if [ ! -z "${SUB_DIRECTORY}" ]; then
cd $SUB_DIRECTORY
fi
From 3d97183d6b2b2b098c83738679467293ae696566 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Fri, 2 Apr 2021 17:52:19 +0200
Subject: [PATCH 015/624] fix(deps): update dependency paragonie/random_compat
to v9 (#1328)
---
appengine/flexible/drupal8/composer.json | 2 +-
appengine/flexible/laravel/composer.json | 2 +-
appengine/flexible/symfony/composer.json | 2 +-
appengine/flexible/wordpress/composer.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/appengine/flexible/drupal8/composer.json b/appengine/flexible/drupal8/composer.json
index 3c23a29d55..de9be9426d 100644
--- a/appengine/flexible/drupal8/composer.json
+++ b/appengine/flexible/drupal8/composer.json
@@ -7,6 +7,6 @@
"monolog/monolog": "^1.19",
"symfony/console": " ^2.7",
"symfony/process": "^3.0",
- "paragonie/random_compat": " ^2.0"
+ "paragonie/random_compat": " ^9.0"
}
}
diff --git a/appengine/flexible/laravel/composer.json b/appengine/flexible/laravel/composer.json
index 7bb4f51de4..53e5784750 100644
--- a/appengine/flexible/laravel/composer.json
+++ b/appengine/flexible/laravel/composer.json
@@ -4,6 +4,6 @@
"symfony\/process": "~2.8|~3.0",
"monolog\/monolog": "^1.19",
"guzzlehttp\/guzzle": "^6.2",
- "paragonie/random_compat": " ^2.0"
+ "paragonie/random_compat": " ^9.0"
}
}
diff --git a/appengine/flexible/symfony/composer.json b/appengine/flexible/symfony/composer.json
index b7b2ea1cd8..fc66faa2a8 100644
--- a/appengine/flexible/symfony/composer.json
+++ b/appengine/flexible/symfony/composer.json
@@ -6,6 +6,6 @@
"symfony/process": "^3.0",
"symfony/yaml": "^3.0",
"google/cloud-logging": "^1.9",
- "paragonie/random_compat": " ^2.0"
+ "paragonie/random_compat": " ^9.0"
}
}
diff --git a/appengine/flexible/wordpress/composer.json b/appengine/flexible/wordpress/composer.json
index e1dda04052..af101e871e 100644
--- a/appengine/flexible/wordpress/composer.json
+++ b/appengine/flexible/wordpress/composer.json
@@ -2,7 +2,7 @@
"require": {
"ext-phar": "*",
"ext-zip": "*",
- "paragonie/random_compat": "^1.3",
+ "paragonie/random_compat": "^9.0",
"symfony/console": "^3.0",
"google/cloud-tools": "^0.12.0"
},
From d5f5da6a8ea2985b52144854afe7d8e89b821c28 Mon Sep 17 00:00:00 2001
From: Ace Nassri
Date: Mon, 5 Apr 2021 09:18:09 -0700
Subject: [PATCH 016/624] feat(functions): add firebase analytics sample
(#1301)
---
CODEOWNERS | 3 +
functions/firebase_analytics/composer.json | 11 ++
functions/firebase_analytics/index.php | 45 ++++++++
functions/firebase_analytics/phpunit.xml.dist | 34 ++++++
.../test/IntegrationTest.php | 106 ++++++++++++++++++
5 files changed, 199 insertions(+)
create mode 100644 functions/firebase_analytics/composer.json
create mode 100644 functions/firebase_analytics/index.php
create mode 100644 functions/firebase_analytics/phpunit.xml.dist
create mode 100644 functions/firebase_analytics/test/IntegrationTest.php
diff --git a/CODEOWNERS b/CODEOWNERS
index 01ff558910..353dc665c2 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -15,3 +15,6 @@
/firestore/ @GoogleCloudPlatform/firestore-dpe @GoogleCloudPlatform/php-admins
/iot/ @gcseh @GoogleCloudPlatform/api-iot @GoogleCloudPlatform/php-admins
/storage/ @GoogleCloudPlatform/storage-dpe @GoogleCloudPlatform/php-admins
+
+# Functions samples owned by the Firebase team
+/functions/firebase_analytics @schandel @samtstern
diff --git a/functions/firebase_analytics/composer.json b/functions/firebase_analytics/composer.json
new file mode 100644
index 0000000000..adb81c98c5
--- /dev/null
+++ b/functions/firebase_analytics/composer.json
@@ -0,0 +1,11 @@
+{
+ "require": {
+ "google/cloud-functions-framework": "^0.7.2"
+ },
+ "scripts": {
+ "start": [
+ "Composer\\Config::disableProcessTimeout",
+ "FUNCTION_SIGNATURE_TYPE=cloudevent FUNCTION_TARGET=firebaseAnalytics php -S localhost:${PORT:-8080} vendor/bin/router.php"
+ ]
+ }
+}
diff --git a/functions/firebase_analytics/index.php b/functions/firebase_analytics/index.php
new file mode 100644
index 0000000000..f4940c2a2d
--- /dev/null
+++ b/functions/firebase_analytics/index.php
@@ -0,0 +1,45 @@
+getData();
+
+ fwrite($log, 'Function triggered by the following event:' . $data['resource'] . PHP_EOL);
+
+ $analyticsEvent = $data['eventDim'][0];
+ $unixTime = $analyticsEvent['timestampMicros'] / 1000;
+
+ fwrite($log, 'Name: ' . $analyticsEvent['name'] . PHP_EOL);
+ fwrite($log, 'Timestamp: ' . gmdate("Y-m-d\TH:i:s\Z", $unixTime) . PHP_EOL);
+
+ $userObj = $data['userDim'];
+ fwrite($log, sprintf(
+ 'Location: %s, %s' . PHP_EOL,
+ $userObj['geoInfo']['city'],
+ $userObj['geoInfo']['country']
+ ));
+
+ fwrite($log, 'Device Model: %s' . $userObj['deviceInfo']['deviceModel'] . PHP_EOL);
+}
+// [END functions_firebase_analytics]
diff --git a/functions/firebase_analytics/phpunit.xml.dist b/functions/firebase_analytics/phpunit.xml.dist
new file mode 100644
index 0000000000..bb8a173ea7
--- /dev/null
+++ b/functions/firebase_analytics/phpunit.xml.dist
@@ -0,0 +1,34 @@
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ .
+
+ ./vendor
+
+
+
+
diff --git a/functions/firebase_analytics/test/IntegrationTest.php b/functions/firebase_analytics/test/IntegrationTest.php
new file mode 100644
index 0000000000..88d83ccf00
--- /dev/null
+++ b/functions/firebase_analytics/test/IntegrationTest.php
@@ -0,0 +1,106 @@
+ CloudEvent::fromArray([
+ 'id' => uniqid(),
+ 'source' => 'firebase.googleapis.com',
+ 'specversion' => '1.0',
+ 'type' => 'google.firebase.remoteconfig.v1.updated',
+ 'data' => [
+ // eventDim is a list of dictionaries
+ 'eventDim' => array([
+ 'name' => 'test_event',
+ 'timestampMicros' => time() * 1000,
+ ]),
+ 'userDim' => [
+ 'geoInfo' => [
+ 'city' => 'San Francisco',
+ 'country' => 'US'
+ ],
+ 'deviceInfo' => [
+ 'deviceModel' => 'Google Pixel XL'
+ ]
+ ]
+ ],
+ ]),
+ 'statusCode' => '200',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testFirebaseAnalytics(
+ CloudEvent $cloudevent,
+ string $statusCode
+ ): void {
+ // Send an HTTP request using CloudEvent.
+ $resp = $this->request($cloudevent);
+
+ // The Cloud Function logs all data to stderr.
+ $actual = self::$localhost->getIncrementalErrorOutput();
+
+ // Confirm the status code.
+ $this->assertEquals($statusCode, $resp->getStatusCode());
+
+ // Verify the data properties are logged by the function.
+ $data = $cloudevent->getData();
+ foreach ($data as $property => $value) {
+ if (is_string($value)) {
+ $this->assertStringContainsString($value, $actual);
+ }
+ }
+ foreach ($data['eventDim'] as $property => $value) {
+ if (is_string($value)) {
+ $this->assertStringContainsString($value, $actual);
+ }
+ }
+ foreach ($data['userDim'] as $property => $value) {
+ if (is_string($value)) {
+ $this->assertStringContainsString($value, $actual);
+ }
+ }
+ }
+}
From 2e256fc2c0637fa791e8d82a1e2a5bb18a26a795 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Mon, 5 Apr 2021 15:57:54 -0700
Subject: [PATCH 017/624] fix: kokoro lint builds and lint script (#1334)
---
.kokoro/lint.cfg | 4 ++--
.kokoro/lint.sh | 33 ---------------------------------
.kokoro/php74.cfg | 4 ----
.kokoro/system_tests.sh | 5 -----
testing/run_cs_check.sh | 6 ++++--
5 files changed, 6 insertions(+), 46 deletions(-)
delete mode 100755 .kokoro/lint.sh
diff --git a/.kokoro/lint.cfg b/.kokoro/lint.cfg
index 160ca627cb..5f9ed254e8 100644
--- a/.kokoro/lint.cfg
+++ b/.kokoro/lint.cfg
@@ -7,6 +7,6 @@ env_vars: {
}
env_vars: {
- key: "RUN_CS_CHECK"
- value: "true"
+ key: "TRAMPOLINE_BUILD_FILE"
+ value: "github/php-docs-samples/testing/run_cs_check.sh"
}
diff --git a/.kokoro/lint.sh b/.kokoro/lint.sh
deleted file mode 100755
index b8c2df59de..0000000000
--- a/.kokoro/lint.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-# Copyright 2021 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -e
-
-if [ "${BASH_DEBUG}" = "true" ]; then
- set -x
-fi
-
-# Kokoro directory for running these samples
-cd github/php-docs-samples
-
-mkdir -p build/logs
-
-export PULL_REQUEST_NUMBER=$KOKORO_GITHUB_PULL_REQUEST_NUMBER
-
-# Run code standards check when appropriate
-if [ "${RUN_CS_CHECK}" = "true" ]; then
- bash testing/run_cs_check.sh
-fi
diff --git a/.kokoro/php74.cfg b/.kokoro/php74.cfg
index f944d803b7..c6409b06a7 100644
--- a/.kokoro/php74.cfg
+++ b/.kokoro/php74.cfg
@@ -15,7 +15,3 @@ env_vars: {
key: "GOOGLE_ALT_CREDENTIALS_FILENAME"
value: "service-account-kokoro1.json"
}
-env_vars: {
- key: "RUN_CS_CHECK"
- value: "true"
-}
diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh
index fe27d64fe0..68a1d37ba8 100755
--- a/.kokoro/system_tests.sh
+++ b/.kokoro/system_tests.sh
@@ -50,11 +50,6 @@ mkdir -p build/logs
export PULL_REQUEST_NUMBER=$KOKORO_GITHUB_PULL_REQUEST_NUMBER
-# Run code standards check when appropriate
-if [ "${RUN_CS_CHECK}" = "true" ]; then
- bash testing/run_cs_check.sh
-fi
-
# If we are running REST tests, disable gRPC
if [ "${RUN_REST_TESTS_ONLY}" = "true" ]; then
GRPC_INI=$(php -i | grep grpc.ini | sed 's/^Additional .ini files parsed => //g' | sed 's/,*$//g' )
diff --git a/testing/run_cs_check.sh b/testing/run_cs_check.sh
index 7f43dcff24..4415407ee4 100755
--- a/testing/run_cs_check.sh
+++ b/testing/run_cs_check.sh
@@ -23,5 +23,7 @@ elif [ -f "./php-cs-fixer" ]; then
PHP_CS_FIXER="./php-cs-fixer"
fi
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-$PHP_CS_FIXER fix --dry-run --diff --config="$DIR/../.php_cs.dist" --path-mode=intersection .
+PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.."
+DIR="${1:-$PROJECT_ROOT}"
+
+$PHP_CS_FIXER fix --dry-run --diff --config="${PROJECT_ROOT}/.php_cs.dist" --path-mode=intersection $DIR
From b651c697c180217cebb4b382c15aa451edfea516 Mon Sep 17 00:00:00 2001
From: Ace Nassri
Date: Mon, 5 Apr 2021 16:17:52 -0700
Subject: [PATCH 018/624] chore: revert SUB_DIRECTORY in favor of
TEST_DIRECTORIES env var (#1335)
---
.kokoro/system_tests.sh | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/.kokoro/system_tests.sh b/.kokoro/system_tests.sh
index 68a1d37ba8..0b0a149f17 100755
--- a/.kokoro/system_tests.sh
+++ b/.kokoro/system_tests.sh
@@ -59,11 +59,5 @@ fi
# Install global test dependencies
composer install -d testing/
-# cd into specific subdirectory (if appropriate)
-MAIN_DIR=$(pwd)
-if [ ! -z "${SUB_DIRECTORY}" ]; then
- cd $SUB_DIRECTORY
-fi
-
# Run tests
-bash $MAIN_DIR/testing/run_test_suite.sh
+bash testing/run_test_suite.sh
From 8b62476bb4ea44e89e5305f476fd30267d33e956 Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Mon, 12 Apr 2021 15:54:06 -0700
Subject: [PATCH 019/624] fix: better error handling and cleaner testing for
imagemagick sample (#1341)
---
functions/imagemagick/index.php | 20 ++++---
functions/imagemagick/test/DeployTest.php | 53 +++++--------------
.../imagemagick/test/IntegrationTest.php | 6 ---
functions/imagemagick/test/TestCasesTrait.php | 8 ++-
4 files changed, 31 insertions(+), 56 deletions(-)
diff --git a/functions/imagemagick/index.php b/functions/imagemagick/index.php
index 4edf0d1023..5ee6f09dd4 100644
--- a/functions/imagemagick/index.php
+++ b/functions/imagemagick/index.php
@@ -20,6 +20,7 @@
use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Vision\V1\ImageAnnotatorClient;
use Google\Cloud\Vision\V1\Likelihood;
+use Google\Rpc\Code;
// [END functions_imagemagick_setup]
@@ -27,6 +28,7 @@
function blurOffensiveImages(CloudEvent $cloudevent): void
{
$log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
+
$storage = new StorageClient();
$data = $cloudevent->getData();
@@ -38,19 +40,21 @@ function blurOffensiveImages(CloudEvent $cloudevent): void
$storage = new StorageClient();
try {
- $request = $annotator->safeSearchDetection($filePath);
- $response = $request->getSafeSearchAnnotation();
+ $response = $annotator->safeSearchDetection($filePath);
- // Handle missing files
- // (This is uncommon, but can happen if race conditions occur)
- if ($response === null) {
- fwrite($log, 'Could not find ' . $filePath . PHP_EOL);
+ // Handle error
+ if ($response->hasError()) {
+ $code = Code::name($response->getError()->getCode());
+ $message = $response->getError()->getMessage();
+ fwrite($log, sprintf('%s: %s' . PHP_EOL, $code, $message));
return;
}
+ $annotation = $response->getSafeSearchAnnotation();
+
$isInappropriate =
- $response->getAdult() === Likelihood::VERY_LIKELY ||
- $response->getViolence() === Likelihood::VERY_LIKELY;
+ $annotation->getAdult() === Likelihood::VERY_LIKELY ||
+ $annotation->getViolence() === Likelihood::VERY_LIKELY;
if ($isInappropriate) {
fwrite($log, 'Detected ' . $data['name'] . ' as inappropriate.' . PHP_EOL);
diff --git a/functions/imagemagick/test/DeployTest.php b/functions/imagemagick/test/DeployTest.php
index e92ab2a922..ea9cbe01d7 100644
--- a/functions/imagemagick/test/DeployTest.php
+++ b/functions/imagemagick/test/DeployTest.php
@@ -20,7 +20,6 @@
namespace Google\Cloud\Samples\Functions\ImageMagick\Test;
use Google\Cloud\Storage\StorageClient;
-use Google\Cloud\Logging\LoggingClient;
use Google\Cloud\TestUtils\CloudFunctionDeploymentTrait;
use PHPUnit\Framework\TestCase;
@@ -40,31 +39,15 @@ class DeployTest extends TestCase
use CloudFunctionDeploymentTrait;
use TestCasesTrait;
- /** @var string */
- private static $entryPoint = 'blurOffensiveImages';
-
- /** @var string */
- private static $functionSignatureType = 'cloudevent';
-
- /** @var string */
- // The test starts by copying images from this bucket.
+ // The test uses this bucket to copy images.
private const FIXTURE_SOURCE_BUCKET = 'cloud-devrel-public';
- /** @var string */
- // This is the bucket the deployed function monitors.
- // The test copies image from FIXTURE_SOURCE_BUCKET to this one.
+ /**
+ * This is the bucket the deployed function monitors.
+ * The test copies image from FIXTURE_SOURCE_BUCKET to this one.
+ */
private static $monitoredBucket;
- /** @var string */
- // The function saves any blurred images to this bucket.
- private static $blurredBucket;
-
- /** @var StorageClient */
- private static $storageClient;
-
- /** @var LoggingClient */
- private static $loggingClient;
-
/**
* @dataProvider cases
*/
@@ -75,16 +58,13 @@ public function testFunction(
$expected,
$statusCode
): void {
- // Upload target file.
- $fixtureBucket = self::$storageClient->bucket(self::FIXTURE_SOURCE_BUCKET);
- $object = $fixtureBucket->object($fileName);
+ // Trigger the cloud storage event by copying the image over
+ $storageClient = new StorageClient();
+ $fixtureBucket = $storageClient->bucket(self::FIXTURE_SOURCE_BUCKET);
+ $object = $fixtureBucket->object($fileName);
$object->copy(self::$monitoredBucket, ['name' => $fileName]);
- // Give event and log systems a head start.
- // If log retrieval fails to find logs for our function within retry limit, increase sleep time.
- sleep(5);
-
$fiveMinAgo = date(\DateTime::RFC3339, strtotime('-5 minutes'));
$this->processFunctionLogs($fiveMinAgo, function (\Iterator $logs) use ($expected, $label) {
// Concatenate all relevant log messages.
@@ -108,20 +88,11 @@ public function testFunction(
private static function doDeploy()
{
// Initialize variables
- if (empty(self::$monitoredBucket)) {
- self::$monitoredBucket = self::requireEnv('GOOGLE_STORAGE_BUCKET');
- }
- if (empty(self::$blurredBucket)) {
- self::$blurredBucket = self::requireEnv('BLURRED_BUCKET_NAME');
- }
-
- if (empty(self::$storageClient)) {
- self::$storageClient = new StorageClient();
- }
+ self::$monitoredBucket = self::requireEnv('GOOGLE_STORAGE_BUCKET');
+ $blurredBucket = self::requireEnv('BLURRED_BUCKET_NAME');
// Forward required env variables to Cloud Functions.
- $envVars = 'GOOGLE_STORAGE_BUCKET=' . self::$monitoredBucket . ',';
- $envVars .= 'BLURRED_BUCKET_NAME=' . self::$blurredBucket;
+ $envVars = sprintf('BLURRED_BUCKET_NAME=%s', $blurredBucket);
self::$fn->deploy(
['--update-env-vars' => $envVars],
diff --git a/functions/imagemagick/test/IntegrationTest.php b/functions/imagemagick/test/IntegrationTest.php
index 0cb0bc711c..36c290be11 100644
--- a/functions/imagemagick/test/IntegrationTest.php
+++ b/functions/imagemagick/test/IntegrationTest.php
@@ -33,12 +33,6 @@ class IntegrationTest extends TestCase
use CloudFunctionLocalTestTrait;
use TestCasesTrait;
- /** @var string */
- private static $entryPoint = 'blurOffensiveImages';
-
- /** @var string */
- private static $functionSignatureType = 'cloudevent';
-
/**
* @dataProvider cases
* @dataProvider integrationCases
diff --git a/functions/imagemagick/test/TestCasesTrait.php b/functions/imagemagick/test/TestCasesTrait.php
index 5066e95e9b..6f2842de4e 100644
--- a/functions/imagemagick/test/TestCasesTrait.php
+++ b/functions/imagemagick/test/TestCasesTrait.php
@@ -25,6 +25,12 @@ trait TestCasesTrait
{
use TestTrait;
+ /** @var string */
+ private static $entryPoint = 'blurOffensiveImages';
+
+ /** @var string */
+ private static $functionSignatureType = 'cloudevent';
+
public static function getDataForFile($fileName): array
{
return [
@@ -90,7 +96,7 @@ public static function integrationCases(): array
'label' => 'Labels missing images as safe',
'filename' => 'does-not-exist.jpg',
'expected' => sprintf(
- 'Could not find gs://%s/does-not-exist.jpg',
+ 'NOT_FOUND: Error opening file: gs://%s/does-not-exist.jpg',
$bucketName
),
'statusCode' => '200'
From 3ea96aba1263d3ae2c400281426d3d729302e27b Mon Sep 17 00:00:00 2001
From: Brent Shaffer
Date: Mon, 12 Apr 2021 17:53:37 -0700
Subject: [PATCH 020/624] fix: functions retry sample and test (#1342)
---
functions/tips_retry/index.php | 8 ++++---
functions/tips_retry/puppies.jpg | Bin 0 -> 286600 bytes
functions/tips_retry/test/DeployTest.php | 20 ++++++------------
functions/tips_retry/test/IntegrationTest.php | 10 ++++++---
4 files changed, 18 insertions(+), 20 deletions(-)
create mode 100644 functions/tips_retry/puppies.jpg
diff --git a/functions/tips_retry/index.php b/functions/tips_retry/index.php
index d585c07457..d3cf22f2f7 100644
--- a/functions/tips_retry/index.php
+++ b/functions/tips_retry/index.php
@@ -21,11 +21,13 @@
function tipsRetry(CloudEvent $event): void
{
- $data = $event->getData()['data'];
- $data = json_decode(base64_decode($data), true);
+ $cloudEventData = $event->getData();
+ $pubSubData = $cloudEventData['message']['data'];
+
+ $json = json_decode(base64_decode($pubSubData), true);
// Determine whether to retry the invocation based on a parameter
- $tryAgain = $data['some_parameter'];
+ $tryAgain = $json['some_parameter'];
if ($tryAgain) {
/**
diff --git a/functions/tips_retry/puppies.jpg b/functions/tips_retry/puppies.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1bfbbc9c5e4147943aaeccfa0c2f8f3435a8e341
GIT binary patch
literal 286600
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%0DjMQc)j*oP;H(~w!6Qa(;NbTM(I^
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