From 53fe67bb84c28b03cf7e82e8f16146de3d39a420 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 19:03:06 +0000 Subject: [PATCH 1/6] added php-mssql --- containers/php-mssql/.devcontainer/Dockerfile | 25 ++ .../php-mssql/.devcontainer/devcontainer.json | 32 ++ containers/php-mssql/.npmignore | 7 + containers/php-mssql/.vscode/launch.json | 48 +++ containers/php-mssql/test-project/main.php | 13 + containers/php-mssql/test-project/phpinfo.php | 3 + containers/php-mssql/test-project/sqlsrv.php | 57 +++ .../php-mssql/test-project/sqlsrvpdo.php | 365 ++++++++++++++++++ .../php-mssql/test-project/test-utils.sh | 133 +++++++ containers/php-mssql/test-project/test.sh | 14 + 10 files changed, 697 insertions(+) create mode 100644 containers/php-mssql/.devcontainer/Dockerfile create mode 100644 containers/php-mssql/.devcontainer/devcontainer.json create mode 100644 containers/php-mssql/.npmignore create mode 100644 containers/php-mssql/.vscode/launch.json create mode 100644 containers/php-mssql/test-project/main.php create mode 100644 containers/php-mssql/test-project/phpinfo.php create mode 100644 containers/php-mssql/test-project/sqlsrv.php create mode 100644 containers/php-mssql/test-project/sqlsrvpdo.php create mode 100644 containers/php-mssql/test-project/test-utils.sh create mode 100755 containers/php-mssql/test-project/test.sh diff --git a/containers/php-mssql/.devcontainer/Dockerfile b/containers/php-mssql/.devcontainer/Dockerfile new file mode 100644 index 0000000000..4e13a81b34 --- /dev/null +++ b/containers/php-mssql/.devcontainer/Dockerfile @@ -0,0 +1,25 @@ +# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.1, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.1-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.1-buster, 8.0-buster, 7-buster, 7.4-buster +ARG VARIANT=8-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/php:${VARIANT} + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# Install MS SQL ODBC +# Ref: https://docs.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac?view=sql-server-ver15#installing-on-debian +RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - +RUN rel=$(lsb_release -r -s) && curl https://packages.microsoft.com/config/debian/$rel/prod.list > /etc/apt/sources.list.d/mssql-release.list +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && ACCEPT_EULA=Y apt-get -y install --no-install-recommends msodbcsql18 mssql-tools18 unixodbc-dev libgssapi-krb5-2 + +# Instal PHP MS SQL Drivers +# Ref: https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15#debian18 +RUN pecl install sqlsrv +RUN pecl install pdo_sqlsrv +RUN printf "; priority=20\nextension=sqlsrv.so\n" > /usr/local/etc/php/conf.d/020-sqlsrv.ini +RUN printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /usr/local/etc/php/conf.d/030-pdo_sqlsrv.ini + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + diff --git a/containers/php-mssql/.devcontainer/devcontainer.json b/containers/php-mssql/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..c5d7c5c1ee --- /dev/null +++ b/containers/php-mssql/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "PHP", + "build": { + "dockerfile": "PHP & MSSQL (Community)", + "args": { + // Update VARIANT to pick a PHP version: 8, 8.1, 8.0, 7, 7.4 + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "VARIANT": "8.1-bullseye", + "NODE_VERSION": "none" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": { + "php.validate.executablePath": "/usr/local/bin/php" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "xdebug.php-debug" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [8080], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/containers/php-mssql/.npmignore b/containers/php-mssql/.npmignore new file mode 100644 index 0000000000..5d84bac182 --- /dev/null +++ b/containers/php-mssql/.npmignore @@ -0,0 +1,7 @@ +README.md +test-project +history +definition-manifest.json +.devcontainer/library-scripts +.vscode +.npmignore diff --git a/containers/php-mssql/.vscode/launch.json b/containers/php-mssql/.vscode/launch.json new file mode 100644 index 0000000000..8432f4452d --- /dev/null +++ b/containers/php-mssql/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 0, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + }, + { + "name": "Launch Built-in web server", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-dxdebug.mode=debug", + "-dxdebug.start_with_request=yes", + "-S", + "localhost:0" + ], + "program": "", + "cwd": "${workspaceRoot}", + "port": 9000, + "serverReadyAction": { + "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + } + ] +} \ No newline at end of file diff --git a/containers/php-mssql/test-project/main.php b/containers/php-mssql/test-project/main.php new file mode 100644 index 0000000000..5ba4f78cb7 --- /dev/null +++ b/containers/php-mssql/test-project/main.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/containers/php-mssql/test-project/phpinfo.php b/containers/php-mssql/test-project/phpinfo.php new file mode 100644 index 0000000000..640e4f2f83 --- /dev/null +++ b/containers/php-mssql/test-project/phpinfo.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/containers/php-mssql/test-project/sqlsrv.php b/containers/php-mssql/test-project/sqlsrv.php new file mode 100644 index 0000000000..7c8530c940 --- /dev/null +++ b/containers/php-mssql/test-project/sqlsrv.php @@ -0,0 +1,57 @@ + "tempdb", + "uid" => "sa", + "pwd" => "A_STR0NG_Passw0rd!" +); + +function exception_handler($exception) { + echo "

Failure

"; + echo "Uncaught exception: " , $exception->getMessage(); + echo "

PHP Info for troubleshooting

"; + phpinfo(); +} + +set_exception_handler('exception_handler'); + +// Establishes the connection +$conn = sqlsrv_connect($serverName, $connectionOptions); +if ($conn === false) { + die(formatErrors(sqlsrv_errors())); +} + +// Select Query +$tsql = "SELECT @@Version AS SQL_VERSION"; + +// Executes the query +$stmt = sqlsrv_query($conn, $tsql); + +// Error handling +if ($stmt === false) { + die(formatErrors(sqlsrv_errors())); +} +?> + +

Success Results :

+ +SQL Error:"; + echo "Error information:
"; + foreach ($errors as $error) { + echo "SQLSTATE: ". $error['SQLSTATE'] . "
"; + echo "Code: ". $error['code'] . "
"; + echo "Message: ". $error['message'] . "
"; + } +} +?> \ No newline at end of file diff --git a/containers/php-mssql/test-project/sqlsrvpdo.php b/containers/php-mssql/test-project/sqlsrvpdo.php new file mode 100644 index 0000000000..813c991067 --- /dev/null +++ b/containers/php-mssql/test-project/sqlsrvpdo.php @@ -0,0 +1,365 @@ + + + + + + + + + AdventureWorks Product Reviews + + + +

AdventureWorks Product Reviews

+
This application is a demonstration of the + object oriented API (PDO_SQLSRV driver) for the + Microsoft Drivers for PHP for SQL Server.

+ ", ""); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + + if (isset($_REQUEST['action'])) { + switch ($_REQUEST['action']) { + /* Get AdventureWorks products by querying against the product name.*/ + case 'getproducts': + try { + $params = array($_POST['query']); + $tsql = "SELECT ProductID, Name, Color, Size, ListPrice + FROM Production.Product + WHERE Name LIKE '%' + ? + '%' AND ListPrice > 0.0"; + + $getProducts = $conn->prepare($tsql); + $getProducts->execute($params); + $products = $getProducts->fetchAll(PDO::FETCH_ASSOC); + $productCount = count($products); + if ($productCount > 0) { + BeginProductsTable($productCount); + foreach ($products as $row) { + PopulateProductsTable($row); + } + EndProductsTable(); + } else { + DisplayNoProdutsMsg(); + } + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + GetSearchTerms(!null); + break; + + /* Get reviews for a specified productID. */ + case 'getreview': + GetPicture($_GET['productid']); + GetReviews($conn, $_GET['productid']); + break; + + /* Write a review for a specified productID. */ + case 'writereview': + DisplayWriteReviewForm($_POST['productid']); + break; + + /* Submit a review to the database. */ + case 'submitreview': + try { + $tsql = "INSERT INTO Production.ProductReview (ProductID, + ReviewerName, + ReviewDate, + EmailAddress, + Rating, + Comments) + VALUES (?,?,?,?,?,?)"; + $params = array( + &$_POST['productid'], + &$_POST['name'], + date("Y-m-d"), + &$_POST['email'], + &$_POST['rating'], + &$_POST['comments'] + ); + $insertReview = $conn->prepare($tsql); + $insertReview->execute($params); + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + GetSearchTerms(true); + GetReviews($conn, $_POST['productid']); + break; + + /* Display form for uploading a picture.*/ + case 'displayuploadpictureform': + try { + $tsql = "SELECT Name FROM Production.Product WHERE ProductID = ?"; + $getName = $conn->prepare($tsql); + $getName->execute(array($_GET['productid'])); + $name = $getName->fetchColumn(0); + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + DisplayUploadPictureForm($_GET['productid'], $name); + break; + + /* Upload a new picture for the selected product. */ + case 'uploadpicture': + try { + $tsql = "INSERT INTO Production.ProductPhoto (LargePhoto) + VALUES (?)"; + $uploadPic = $conn->prepare($tsql); + $fileStream = fopen($_FILES['file']['tmp_name'], "r"); + $uploadPic->bindParam( + 1, + $fileStream, + PDO::PARAM_LOB, + 0, + PDO::SQLSRV_ENCODING_BINARY + ); + $uploadPic->execute(); + + /* Get the first field - the identity from INSERT - + so we can associate it with the product ID. */ + $photoID = $conn->lastInsertId(); + $tsql = "UPDATE Production.ProductProductPhoto + SET ProductPhotoID = ? + WHERE ProductID = ?"; + $associateIds = $conn->prepare($tsql); + $associateIds->execute(array($photoID, $_POST['productid'])); + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + + GetPicture($_POST['productid']); + DisplayWriteReviewButton($_POST['productid']); + GetSearchTerms(!null); + break; + } //End Switch + } else { + GetSearchTerms(!null); + } + + function GetPicture($productID) + { + echo ""; + echo ""; + echo "
"; + echo "
Upload new picture.

"; + } + + function GetReviews($conn, $productID) + { + try { + $tsql = "SELECT ReviewerName, +CONVERT(varchar(32), +ReviewDate, 107) AS [ReviewDate], +Rating, +Comments + FROM Production.ProductReview + WHERE ProductID = ? + ORDER BY ReviewDate DESC"; + $getReviews = $conn->prepare($tsql); + $getReviews->execute(array($productID)); + $reviews = $getReviews->fetchAll(PDO::FETCH_NUM); + $reviewCount = count($reviews); + if ($reviewCount > 0) { + foreach ($reviews as $row) { + $name = $row[0]; + $date = $row[1]; + $rating = $row[2]; + $comments = $row[3]; + DisplayReview($productID, $name, $date, $rating, $comments); + } + } else { + DisplayNoReviewsMsg(); + } + } catch (Exception $e) { + die(print_r($e->getMessage())); + } + DisplayWriteReviewButton($productID); + GetSearchTerms(!null); + } + + /*** Presentation and Utility Functions ***/ + + function BeginProductsTable($rowCount) + { + /* Display the beginning of the search results table. */ + $headings = array("Product ID", "Product Name", "Color", "Size", "Price"); + echo ""; + echo "$rowCount Results"; + foreach ($headings as $heading) { + echo ""; + } + echo ""; + } + + function DisplayNoProdutsMsg() + { + echo "

No products found.

"; + } + + function DisplayNoReviewsMsg() + { + echo "

There are no reviews for this product.

"; + } + + function DisplayReview($productID, $name, $date, $rating, $comments) + { + /* Display a product review. */ + echo "
$heading
"; + echo " + + + + + "; + echo " + + + + + + +
ProductIDReviewerDateRating
$productID$name$date$rating
$comments


"; + } + + function DisplayUploadPictureForm($productID, $name) + { + echo "

Upload Picture

"; + echo "

$name

"; + echo "
+ + + + + + + + + +
+ +
+ +
+
"; + } + + function DisplayWriteReviewButton($productID) + { + echo " + + + +

"; + } + + function DisplayWriteReviewForm($productID) + { + /* Display the form for entering a product review. */ + echo "
Name, E-mail, and Rating are required fields.
"; + echo " + + + + + + + + + + + + + + + + + + + + + + + +
Name:
E-mail:
Rating: 12345
+ +
+

+

"; + } + + function EndProductsTable() + { + echo "
"; + } + + function GetSearchTerms($success) + { + /* Get and submit terms for searching the database. */ + if (is_null($success)) { + echo "

Review successfully submitted.

"; + } + echo "

Enter search terms to find products.

"; + echo " + + + + + + + + + +
"; + } + + function PopulateProductsTable($values) + { + /* Populate Products table with search results. */ + $productID = $values['ProductID']; + echo ""; + foreach ($values as $key => $value) { + if (0 == strcasecmp("Name", $key)) { + echo "$value"; + } elseif (!is_null($value)) { + if (0 == strcasecmp("ListPrice", $key)) { + /* Format with two digits of precision. */ + $formattedPrice = sprintf("%.2f", $value); + echo "$$formattedPrice"; + } else { + echo "$value"; + } + } else { + echo "N/A"; + } + } + echo " +
+ + + + +
"; + } + ?> + + + \ No newline at end of file diff --git a/containers/php-mssql/test-project/test-utils.sh b/containers/php-mssql/test-project/test-utils.sh new file mode 100644 index 0000000000..3d7cfcfe3c --- /dev/null +++ b/containers/php-mssql/test-project/test-utils.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echo "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echo "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echo "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echo -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + checkMultiple "vscode-server" 1 "[ -d $HOME/.vscode-server/bin ]" "[ -d $HOME/.vscode-server-insiders/bin ]" "[ -d $HOME/.vscode-test-server/bin ]" "[ -d $HOME/.vscode-remote/bin ]" "[ -d $HOME/.vscode-remote/bin ]" + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echo -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} \ No newline at end of file diff --git a/containers/php-mssql/test-project/test.sh b/containers/php-mssql/test-project/test.sh new file mode 100755 index 0000000000..9f149bbb24 --- /dev/null +++ b/containers/php-mssql/test-project/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Actual tests +checkExtension "xdebug.php-debug" +check "php" php --version + +# Report result +reportResults From 6c714d6bcf365ab8d399889ddcba829b658858d2 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 15:17:15 -0700 Subject: [PATCH 2/6] included mssql in docker container --- containers/php-mssql/.devcontainer/Dockerfile | 9 ++-- .../php-mssql/.devcontainer/devcontainer.json | 17 +++---- .../.devcontainer/docker-compose.yml | 44 +++++++++++++++++++ 3 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 containers/php-mssql/.devcontainer/docker-compose.yml diff --git a/containers/php-mssql/.devcontainer/Dockerfile b/containers/php-mssql/.devcontainer/Dockerfile index 4e13a81b34..ff18c50dd9 100644 --- a/containers/php-mssql/.devcontainer/Dockerfile +++ b/containers/php-mssql/.devcontainer/Dockerfile @@ -15,10 +15,11 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # Instal PHP MS SQL Drivers # Ref: https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver15#debian18 -RUN pecl install sqlsrv -RUN pecl install pdo_sqlsrv -RUN printf "; priority=20\nextension=sqlsrv.so\n" > /usr/local/etc/php/conf.d/020-sqlsrv.ini -RUN printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /usr/local/etc/php/conf.d/030-pdo_sqlsrv.ini +RUN pecl install sqlsrv && pecl install pdo_sqlsrv +RUN printf "; priority=20\nextension=sqlsrv.so\n" > /usr/local/etc/php/conf.d/020-sqlsrv.ini \ + && printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /usr/local/etc/php/conf.d/030-pdo_sqlsrv.ini + +ENV PATH="$PATH:/opt/mssql-tools18/bin" # [Optional] Uncomment this line to install global node packages. # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/containers/php-mssql/.devcontainer/devcontainer.json b/containers/php-mssql/.devcontainer/devcontainer.json index c5d7c5c1ee..f2e1de2d03 100644 --- a/containers/php-mssql/.devcontainer/devcontainer.json +++ b/containers/php-mssql/.devcontainer/devcontainer.json @@ -1,15 +1,8 @@ { - "name": "PHP", - "build": { - "dockerfile": "PHP & MSSQL (Community)", - "args": { - // Update VARIANT to pick a PHP version: 8, 8.1, 8.0, 7, 7.4 - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "8.1-bullseye", - "NODE_VERSION": "none" - } - }, + "name": "PHP & MSSQL (Community)", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", // Set *default* container specific settings.json values on container create. "settings": { @@ -22,7 +15,7 @@ ], // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8080], + "forwardPorts": [ 8080, 1433 ], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" diff --git a/containers/php-mssql/.devcontainer/docker-compose.yml b/containers/php-mssql/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000..d3e87ad05b --- /dev/null +++ b/containers/php-mssql/.devcontainer/docker-compose.yml @@ -0,0 +1,44 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + # Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4 + # Append -bullseye or -buster to pin to an OS version. + # Use -bullseye variants on local arm64/Apple Silicon. + VARIANT: "8-bullseye" + # Optional Node.js version + NODE_VERSION: "none" + + + volumes: + - ..:/workspace:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + + # Uncomment the next line to use a non-root user for all processes. + # user: vscode + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: mcr.microsoft.com/mssql/server:2019-latest + restart: unless-stopped + ports: + - "1433:1433" + volumes: + - mssql_volume:/var/opt/mssql + environment: + ACCEPT_EULA: Y + SA_PASSWORD: A_STR0NG_Passw0rd! + +volumes: + mssql_volume: From 204f67c2c53013b949cdc2c36a26f473d8d97d42 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 15:17:25 -0700 Subject: [PATCH 3/6] added samples --- containers/php-mssql/.vscode/launch.json | 40 +- containers/php-mssql/test-project/sqlsrv.php | 5 +- .../php-mssql/test-project/sqlsrvpdo.php | 389 ++---------------- 3 files changed, 47 insertions(+), 387 deletions(-) diff --git a/containers/php-mssql/.vscode/launch.json b/containers/php-mssql/.vscode/launch.json index 8432f4452d..d728d84074 100644 --- a/containers/php-mssql/.vscode/launch.json +++ b/containers/php-mssql/.vscode/launch.json @@ -4,6 +4,25 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Launch Built-in web server", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-dxdebug.mode=debug", + "-dxdebug.start_with_request=yes", + "-S", + "localhost:0" + ], + "program": "", + "cwd": "${workspaceRoot}", + "port": 9000, + "serverReadyAction": { + "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", + "uriFormat": "http://localhost:%s/test-project/sqlsrv.php", + "action": "openExternally" + } + }, { "name": "Listen for Xdebug", "type": "php", @@ -24,25 +43,6 @@ "XDEBUG_MODE": "debug,develop", "XDEBUG_CONFIG": "client_port=${port}" } - }, - { - "name": "Launch Built-in web server", - "type": "php", - "request": "launch", - "runtimeArgs": [ - "-dxdebug.mode=debug", - "-dxdebug.start_with_request=yes", - "-S", - "localhost:0" - ], - "program": "", - "cwd": "${workspaceRoot}", - "port": 9000, - "serverReadyAction": { - "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", - "uriFormat": "http://localhost:%s", - "action": "openExternally" - } - } + } ] } \ No newline at end of file diff --git a/containers/php-mssql/test-project/sqlsrv.php b/containers/php-mssql/test-project/sqlsrv.php index 7c8530c940..14a2cff387 100644 --- a/containers/php-mssql/test-project/sqlsrv.php +++ b/containers/php-mssql/test-project/sqlsrv.php @@ -3,7 +3,8 @@ $connectionOptions = array( "database" => "tempdb", "uid" => "sa", - "pwd" => "A_STR0NG_Passw0rd!" + "pwd" => "A_STR0NG_Passw0rd!", + "TrustServerCertificate" => "1" ); function exception_handler($exception) { @@ -33,7 +34,7 @@ function exception_handler($exception) { } ?> -

Success Results :

+

Azure SQL / SQL Server Connection :

- - - - - - - - AdventureWorks Product Reviews - - - -

AdventureWorks Product Reviews

-
This application is a demonstration of the - object oriented API (PDO_SQLSRV driver) for the - Microsoft Drivers for PHP for SQL Server.

- ", ""); - $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - - if (isset($_REQUEST['action'])) { - switch ($_REQUEST['action']) { - /* Get AdventureWorks products by querying against the product name.*/ - case 'getproducts': - try { - $params = array($_POST['query']); - $tsql = "SELECT ProductID, Name, Color, Size, ListPrice - FROM Production.Product - WHERE Name LIKE '%' + ? + '%' AND ListPrice > 0.0"; - - $getProducts = $conn->prepare($tsql); - $getProducts->execute($params); - $products = $getProducts->fetchAll(PDO::FETCH_ASSOC); - $productCount = count($products); - if ($productCount > 0) { - BeginProductsTable($productCount); - foreach ($products as $row) { - PopulateProductsTable($row); - } - EndProductsTable(); - } else { - DisplayNoProdutsMsg(); - } - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - GetSearchTerms(!null); - break; - - /* Get reviews for a specified productID. */ - case 'getreview': - GetPicture($_GET['productid']); - GetReviews($conn, $_GET['productid']); - break; - - /* Write a review for a specified productID. */ - case 'writereview': - DisplayWriteReviewForm($_POST['productid']); - break; - - /* Submit a review to the database. */ - case 'submitreview': - try { - $tsql = "INSERT INTO Production.ProductReview (ProductID, - ReviewerName, - ReviewDate, - EmailAddress, - Rating, - Comments) - VALUES (?,?,?,?,?,?)"; - $params = array( - &$_POST['productid'], - &$_POST['name'], - date("Y-m-d"), - &$_POST['email'], - &$_POST['rating'], - &$_POST['comments'] - ); - $insertReview = $conn->prepare($tsql); - $insertReview->execute($params); - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - GetSearchTerms(true); - GetReviews($conn, $_POST['productid']); - break; - - /* Display form for uploading a picture.*/ - case 'displayuploadpictureform': - try { - $tsql = "SELECT Name FROM Production.Product WHERE ProductID = ?"; - $getName = $conn->prepare($tsql); - $getName->execute(array($_GET['productid'])); - $name = $getName->fetchColumn(0); - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - DisplayUploadPictureForm($_GET['productid'], $name); - break; - - /* Upload a new picture for the selected product. */ - case 'uploadpicture': - try { - $tsql = "INSERT INTO Production.ProductPhoto (LargePhoto) - VALUES (?)"; - $uploadPic = $conn->prepare($tsql); - $fileStream = fopen($_FILES['file']['tmp_name'], "r"); - $uploadPic->bindParam( - 1, - $fileStream, - PDO::PARAM_LOB, - 0, - PDO::SQLSRV_ENCODING_BINARY - ); - $uploadPic->execute(); - - /* Get the first field - the identity from INSERT - - so we can associate it with the product ID. */ - $photoID = $conn->lastInsertId(); - $tsql = "UPDATE Production.ProductProductPhoto - SET ProductPhotoID = ? - WHERE ProductID = ?"; - $associateIds = $conn->prepare($tsql); - $associateIds->execute(array($photoID, $_POST['productid'])); - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - - GetPicture($_POST['productid']); - DisplayWriteReviewButton($_POST['productid']); - GetSearchTerms(!null); - break; - } //End Switch - } else { - GetSearchTerms(!null); - } - - function GetPicture($productID) - { - echo ""; - echo ""; - echo "
"; - echo "
Upload new picture.

"; - } - - function GetReviews($conn, $productID) - { - try { - $tsql = "SELECT ReviewerName, -CONVERT(varchar(32), -ReviewDate, 107) AS [ReviewDate], -Rating, -Comments - FROM Production.ProductReview - WHERE ProductID = ? - ORDER BY ReviewDate DESC"; - $getReviews = $conn->prepare($tsql); - $getReviews->execute(array($productID)); - $reviews = $getReviews->fetchAll(PDO::FETCH_NUM); - $reviewCount = count($reviews); - if ($reviewCount > 0) { - foreach ($reviews as $row) { - $name = $row[0]; - $date = $row[1]; - $rating = $row[2]; - $comments = $row[3]; - DisplayReview($productID, $name, $date, $rating, $comments); - } - } else { - DisplayNoReviewsMsg(); - } - } catch (Exception $e) { - die(print_r($e->getMessage())); - } - DisplayWriteReviewButton($productID); - GetSearchTerms(!null); - } - - /*** Presentation and Utility Functions ***/ - - function BeginProductsTable($rowCount) - { - /* Display the beginning of the search results table. */ - $headings = array("Product ID", "Product Name", "Color", "Size", "Price"); - echo ""; - echo "$rowCount Results"; - foreach ($headings as $heading) { - echo ""; - } - echo ""; - } - - function DisplayNoProdutsMsg() - { - echo "

No products found.

"; - } - - function DisplayNoReviewsMsg() - { - echo "

There are no reviews for this product.

"; - } - - function DisplayReview($productID, $name, $date, $rating, $comments) - { - /* Display a product review. */ - echo "
$heading
"; - echo " - - - - - "; - echo " - - - - - - -
ProductIDReviewerDateRating
$productID$name$date$rating
$comments


"; - } - - function DisplayUploadPictureForm($productID, $name) - { - echo "

Upload Picture

"; - echo "

$name

"; - echo "
- - - - - - - - - -
- -
- -
-
"; - } - - function DisplayWriteReviewButton($productID) - { - echo " - - - -

"; - } - - function DisplayWriteReviewForm($productID) - { - /* Display the form for entering a product review. */ - echo "
Name, E-mail, and Rating are required fields.
"; - echo " - - - - - - - - - - - - - - - - - - - - - - - -
Name:
E-mail:
Rating: 12345
- -
-

-

"; - } - - function EndProductsTable() - { - echo "
"; - } - - function GetSearchTerms($success) - { - /* Get and submit terms for searching the database. */ - if (is_null($success)) { - echo "

Review successfully submitted.

"; - } - echo "

Enter search terms to find products.

"; - echo " - - - - - - - - - -
"; - } - - function PopulateProductsTable($values) - { - /* Populate Products table with search results. */ - $productID = $values['ProductID']; - echo ""; - foreach ($values as $key => $value) { - if (0 == strcasecmp("Name", $key)) { - echo "$value"; - } elseif (!is_null($value)) { - if (0 == strcasecmp("ListPrice", $key)) { - /* Format with two digits of precision. */ - $formattedPrice = sprintf("%.2f", $value); - echo "$$formattedPrice"; - } else { - echo "$value"; - } - } else { - echo "N/A"; - } - } - echo " -
- - - - -
"; - } - ?> - - - \ No newline at end of file +Failure"; + echo "Uncaught exception: " , $exception->getMessage(); + #echo "

PHP Info for troubleshooting

"; + #phpinfo(); +} + +set_exception_handler('exception_handler'); + +?> + +

Azure SQL / SQL Server Connection :

+ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$query = "SELECT @@Version AS SQL_VERSION"; +$stmt = $conn->query( $query ); + +while ($row = $stmt->fetch( PDO::FETCH_ASSOC )) { + echo $row['SQL_VERSION'] . PHP_EOL; +} +?> \ No newline at end of file From 6854ec3aa3954452d7433c0d4ddf962a01ab6db8 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 15:53:09 -0700 Subject: [PATCH 4/6] using php 8.1 --- containers/php-mssql/.devcontainer/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/containers/php-mssql/.devcontainer/docker-compose.yml b/containers/php-mssql/.devcontainer/docker-compose.yml index d3e87ad05b..30508bade9 100644 --- a/containers/php-mssql/.devcontainer/docker-compose.yml +++ b/containers/php-mssql/.devcontainer/docker-compose.yml @@ -9,7 +9,7 @@ services: # Update 'VARIANT' to pick a version of PHP version: 8, 8.1, 8.0, 7, 7.4 # Append -bullseye or -buster to pin to an OS version. # Use -bullseye variants on local arm64/Apple Silicon. - VARIANT: "8-bullseye" + VARIANT: "8.1-bullseye" # Optional Node.js version NODE_VERSION: "none" From c96adea4402bbb2349822fbdeafdb86e5bd71521 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 15:53:28 -0700 Subject: [PATCH 5/6] added vscode mssql ext --- .../php-mssql/.devcontainer/devcontainer.json | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/containers/php-mssql/.devcontainer/devcontainer.json b/containers/php-mssql/.devcontainer/devcontainer.json index f2e1de2d03..eee6baea52 100644 --- a/containers/php-mssql/.devcontainer/devcontainer.json +++ b/containers/php-mssql/.devcontainer/devcontainer.json @@ -6,12 +6,24 @@ // Set *default* container specific settings.json values on container create. "settings": { - "php.validate.executablePath": "/usr/local/bin/php" + "php.validate.executablePath": "/usr/local/bin/php", + "mssql.connections": [ + { + "server": "localhost", + "database": "", + "authenticationType": "SqlLogin", + "user": "sa", + "password": "A_STR0NG_Passw0rd!", + "emptyPasswordInput": false, + "savePassword": true, + "profileName": "php-mssql-container" + } + ] }, - // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "xdebug.php-debug" + "xdebug.php-debug", + "ms-mssql.mssql" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. @@ -21,5 +33,11 @@ // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + "remoteUser": "vscode", + + // Supported features + "features": { + "github-cli": "latest", + "azure-cli": "latest" + } } From 41ef70d01e7917dc09970a0cf3993f2f6470b335 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 16 May 2022 15:53:35 -0700 Subject: [PATCH 6/6] updated readme --- containers/php-mssql/README.md | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 containers/php-mssql/README.md diff --git a/containers/php-mssql/README.md b/containers/php-mssql/README.md new file mode 100644 index 0000000000..1a66f60563 --- /dev/null +++ b/containers/php-mssql/README.md @@ -0,0 +1,66 @@ +# PHP & Azure SQL/SQL Server (Community) + +## Summary + +*Develop PHP based applications using Azure SQL DB, Azure SQL MI or SQL Server. Includes needed tools, extensions, dependencies and samples* + +| Metadata | Value | +|----------|-------| +| *Contributors* | [Davide Mauri]() | +| *Categories* | Community, Languages | +| *Definition type* | Docker Compose | +| *Published images* | mcr.microsoft.com/vscode/devcontainers/php | +| *Container host OS support* | Linux, macOS, Windows | +| *Container OS* | Debian | +| *Languages, platforms* | PHP, MS SQL Server | + +## Description + +This definition creates two containers, one for PHP and one for Microsoft SQL Server (MSSQL). Code will attach to the PHP container, and from within that container the MSSQL container will be available on localhost port 1433. The MSSQL instance can be managed from the container's command line with: + +``` +sqlcmd -S localhost -U sa -P A_STR0NG_Passw0rd! -C +``` + +Or connecting with [Microsoft SQL Server Management Studio](https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms) or [Azure Data Studio](https://docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio) or the [MSSQL Server VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-mssql.mssql). The VS Code Extension is automatically installed and configured for accessing the Microsoft SQL Server instance running in the dedicated container. Look for the profile `php-mssql-container` in the SQL Server tab. + +## Using this definition + +This definition has been created using the PHP defintion as starting point. It has been tested using the `8.1-bullseye` variant (Debian Buster and PHP 8.1.). Please refer to the PHP devcontainer repository for any additional information on how to customize the image: + +https://github.com/microsoft/vscode-dev-containers/tree/main/containers/php + + +## Testing the definition + +This definition includes some test code that will help you verify it is working as expected on your system. Follow these steps: + +1. If this is your first time using a development container, please follow the [getting started steps](https://aka.ms/vscode-remote/containers/getting-started) to set up your machine. +2. Clone this repository. +3. Start VS Code, press F1, and select **Remote-Containers: Open Folder in Container...** +4. Select the `containers/php` folder. +5. After the folder has opened in the container, press F5 to start the project. +6. You should see "Hello remote world!" in the Debug Console after the program executes. +7. From here, you can add breakpoints or edit the contents of the `test-project` folder to do further testing. + +## Testing PHP and SQL Server + +The defintion include couple of files to test that PHP can successfuly connect and query SQL Server or Azure SQL. The files are in the `test-project` folder: `sqlsrv.php` and `sqlsrvdpo.php`. + +They will both try to connect to the MS SQL Server running in the container and will query the server version. To test if everything works you can press F5 so that the debug configuration `Launch Built-in web server` will be launched. It will open a browser and automatically navigate to: + +http://localhost:37135/test-project/sqlsrv.php + +to check the everything working. If it does you'll see something like: + +``` +Microsoft SQL Server 2019 (RTM-CU15) (KB5008996) - 15.0.4198.2 (X64) Jan 12 2022 22:30:08 Copyright (C) 2019 Microsoft Corporation Developer Edition (64-bit) on Linux (Ubuntu 20.04.3 LTS) +``` + +You can also set breakpoints, as dubugging is supported via XDebug. + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the MIT License. See [LICENSE](https://github.com/microsoft/vscode-dev-containers/blob/main/LICENSE). \ No newline at end of file