Git merge
Back to home
On this page
You have a separate environment with separate data. Next, add a service to your preview environment.
Add a service
Platform.sh includes many services such as databases, cache, and search engines. These are included in your project, so you can manage them with Git and back them up with your project.
Add a database service (or choose another service) by following these steps:
-
Create a services configuration file.
touch .platform/services.yaml
This file holds the configuration for all services your app needs.
-
Add a database in that file. (If you need a different database service, you can choose from the available services. Then change the
type
to fit your choice.).platform/services.yamldb: type: mariadb:10.5 disk: 1024
Note that
db
is the name of the service. You can give it any name you want with lowercase alphanumeric characters, hyphens, and underscores. -
Add a relationship between the database and your app in your app configuration:
.platform.app.yamlrelationships: database: "db:mysql"
This relationship is where connections are made. The
database
is the name of the relationship, which you can change if you want. Thedb
has to be the same as the service name from step 2. -
Commit your changes and push:
git add . git commit -m "Add database and connect to app" platform push
Now you have a database you can connect to your app.
Connect database to app
Now connect the database to your app.
First, add the Platform.sh Config Reader library to make the connection easier.
composer require platformsh/config-reader
npm install platformsh-config
pip install platformshconfig
Then connect to the database in your app using the library. You can choose where to do this depending on what makes sense in your app.
<?php
declare(strict_types=1);
use Platformsh\ConfigReader\Config;
// Create a new config object to ease reading the Platform.sh environment variables.
// You can alternatively use getenv() yourself.
$config = new Config();
// The 'database' relationship is generally the name of primary SQL database of an application.
// That's not required, but much of our default automation code assumes it.
$credentials = $config->credentials('database');
try {
// Connect to the database using PDO. If using some other abstraction layer you would
// inject the values from $database into whatever your abstraction layer asks for.
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s', $credentials['host'], $credentials['port'], $credentials['path']);
$conn = new \PDO($dsn, $credentials['username'], $credentials['password'], [
// Always use Exception error mode with PDO, as it's more reliable.
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
// So we don't have to mess around with cursors and unbuffered queries by default.
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
// Make sure MySQL returns all matched rows on update queries including
// rows that actually didn't have to be updated because the values didn't
// change. This matches common behavior among other database systems.
\PDO::MYSQL_ATTR_FOUND_ROWS => TRUE,
]);
// Creating a table.
$sql = "CREATE TABLE People (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
city VARCHAR(30) NOT NULL
)";
$conn->query($sql);
// Insert data.
$sql = "INSERT INTO People (name, city) VALUES
('Neil Armstrong', 'Moon'),
('Buzz Aldrin', 'Glen Ridge'),
('Sally Ride', 'La Jolla');";
$conn->query($sql);
// Show table.
$sql = "SELECT * FROM People";
$result = $conn->query($sql);
$result->setFetchMode(\PDO::FETCH_OBJ);
if ($result) {
print <<<TABLE
<table>
<thead>
<tr><th>Name</th><th>City</th></tr>
</thead>
<tbody>
TABLE;
foreach ($result as $record) {
printf("<tr><td>%s</td><td>%s</td></tr>\n", $record->name, $record->city);
}
print "</tbody>\n</table>\n";
}
// Drop table
$sql = "DROP TABLE People";
$conn->query($sql);
} catch (\Exception $e) {
print $e->getMessage();
}
const mysql = require("mysql2/promise");
const config = require("platformsh-config").config();
exports.usageExample = async function () {
const credentials = config.credentials("database");
const connection = await mysql.createConnection({
host: credentials.host,
port: credentials.port,
user: credentials.username,
password: credentials.password,
database: credentials.path,
});
// Creating a table.
await connection.query(
`CREATE TABLE IF NOT EXISTS People (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
city VARCHAR(30) NOT NULL
)`
);
// Insert data.
await connection.query(
`INSERT INTO People (name, city)
VALUES
('Neil Armstrong', 'Moon'),
('Buzz Aldrin', 'Glen Ridge'),
('Sally Ride', 'La Jolla');`
);
// Show table.
const [rows] = await connection.query("SELECT * FROM People");
// Drop table.
await connection.query("DROP TABLE People");
const outputRows = rows
.map(({ name, city }) => `<tr><td>${name}</td><td>${city}</td></tr>\n`)
.join("\n");
return `
<table>
<thead>
<tr>
<th>Name</th><th>City</th>
</tr>
</thhead>
<tbody>
${outputRows}
</tbody>
</table>
`;
};
import pymysql
from platformshconfig import Config
def usage_example():
# Create a new Config object to ease reading the Platform.sh environment variables.
# You can alternatively use os.environ yourself.
config = Config()
# The 'database' relationship is generally the name of primary SQL database of an application.
# That's not required, but much of our default automation code assumes it.'
credentials = config.credentials('database')
try:
# Connect to the database using PDO. If using some other abstraction layer you would inject the values
# from `database` into whatever your abstraction layer asks for.
conn = pymysql.connect(host=credentials['host'],
port=credentials['port'],
database=credentials['path'],
user=credentials['username'],
password=credentials['password'])
sql = '''
CREATE TABLE People (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
city VARCHAR(30) NOT NULL
)
'''
cur = conn.cursor()
cur.execute(sql)
sql = '''
INSERT INTO People (name, city) VALUES
('Neil Armstrong', 'Moon'),
('Buzz Aldrin', 'Glen Ridge'),
('Sally Ride', 'La Jolla');
'''
cur.execute(sql)
# Show table.
sql = '''SELECT * FROM People'''
cur.execute(sql)
result = cur.fetchall()
table = '''<table>
<thead>
<tr><th>Name</th><th>City</th></tr>
</thead>
<tbody>'''
if result:
for record in result:
table += '''<tr><td>{0}</td><td>{1}</td><tr>\n'''.format(record[1], record[2])
table += '''</tbody>\n</table>\n'''
# Drop table
sql = '''DROP TABLE People'''
cur.execute(sql)
# Close communication with the database
cur.close()
conn.close()
return table
except Exception as e:
return e
package examples
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
psh "github.com/platformsh/config-reader-go/v2"
sqldsn "github.com/platformsh/config-reader-go/v2/sqldsn"
)
func UsageExampleMySQL() string {
// Create a NewRuntimeConfig object to ease reading the Platform.sh environment variables.
// You can alternatively use os.Getenv() yourself.
config, err := psh.NewRuntimeConfig()
checkErr(err)
// The 'database' relationship is generally the name of the primary SQL database of an application.
// That's not required, but much of our default automation code assumes it.
credentials, err := config.Credentials("database")
checkErr(err)
// Using the sqldsn formatted credentials package.
formatted, err := sqldsn.FormattedCredentials(credentials)
checkErr(err)
db, err := sql.Open("mysql", formatted)
checkErr(err)
defer db.Close()
// Force MySQL into modern mode.
db.Exec("SET NAMES=utf8")
db.Exec("SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES," +
"NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO," +
"NO_AUTO_CREATE_USER,ONLY_FULL_GROUP_BY'")
// Creating a table.
sqlCreate := "CREATE TABLE IF NOT EXISTS People (" +
"id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY," +
"name VARCHAR(30) NOT NULL," +
"city VARCHAR(30) NOT NULL)"
_, err = db.Exec(sqlCreate)
checkErr(err)
// Insert data.
sqlInsert := "INSERT INTO People (name, city) VALUES" +
"('Neil Armstrong', 'Moon')," +
"('Buzz Aldrin', 'Glen Ridge')," +
"('Sally Ride', 'La Jolla');"
_, err = db.Exec(sqlInsert)
checkErr(err)
table := "<table>" +
"<thead>" +
"<tr><th>Name</th><th>City</th></tr>" +
"</thead>" +
"<tbody>"
var id int
var name string
var city string
rows, err := db.Query("SELECT * FROM People")
if err != nil {
panic(err)
} else {
for rows.Next() {
err = rows.Scan(&id, &name, &city)
checkErr(err)
table += fmt.Sprintf("<tr><td>%s</td><td>%s</td><tr>\n", name, city)
}
table += "</tbody>\n</table>\n"
}
_, err = db.Exec("DROP TABLE People;")
checkErr(err)
return table
}
package sh.platform.languages.sample;
import sh.platform.config.Config;
import sh.platform.config.MySQL;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.function.Supplier;
public class MySQLSample implements Supplier<String> {
@Override
public String get() {
StringBuilder logger = new StringBuilder();
// Create a new config object to ease reading the Platform.sh environment variables.
// You can alternatively use getenv() yourself.
Config config = new Config();
// The 'database' relationship is generally the name of primary SQL database of an application.
// That's not required, but much of our default automation code assumes it.
MySQL database = config.getCredential("database", MySQL::new);
DataSource dataSource = database.get();
// Connect to the database
try (Connection connection = dataSource.getConnection()) {
// Creating a table.
String sql = "CREATE TABLE IF NOT EXISTS People (" +
" id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY," +
"name VARCHAR(30) NOT NULL," +
"city VARCHAR(30) NOT NULL)";
final Statement statement = connection.createStatement();
statement.execute(sql);
// Insert data.
sql = "INSERT INTO People (name, city) VALUES" +
"('Neil Armstrong', 'Moon')," +
"('Buzz Aldrin', 'Glen Ridge')," +
"('Sally Ride', 'La Jolla')";
statement.execute(sql);
// Show table.
sql = "SELECT * FROM People";
final ResultSet resultSet = statement.executeQuery(sql);
logger.append("<table><thead><tr><th>Name</th><th>City</th></tr></thhead><tbody>");
while (resultSet.next()) {
String name = resultSet.getString("name");
String city = resultSet.getString("city");
logger.append(String.format("<tr><td>%s</td><td>%s</td></tr>", name, city));
logger.append('\n');
}
logger.append("</tbody></table>");
statement.execute("DROP TABLE People");
return logger.toString();
} catch (SQLException exp) {
throw new RuntimeException("An error when execute MySQL", exp);
}
}
}
This example creates a table in your database, adds some data, prints the data as an HTML table, and deletes the database table. If you commit your changes and push, you see the HTML table in your built app.
Merge your changes
You added the database to the dev
environment.
To have a database in your production environment, merge your changes.
platform merge dev
Now your production branch has its own database.
Data inheritance
Data is inherited from parent environments, but not from child environments. So when you branch an environment (or later sync it), it copies data from its parent. But when you merge an environment, its data isn’t automatically copied into its parent.
This allows you to test your setup with production data so you can be sure changes work in production. At the same time, your testing has no effect on the production data so you don’t have to worry about issues there.
Data in child environments
To see how the data in child environments is separate, follow these steps:
-
Add a table to your
dev
database:platform sql --environment dev 'CREATE TABLE child_data (a int); INSERT INTO child_data(a) VALUES (1), (2), (3);'
-
See the data in the
dev
database:platform sql --environment dev 'SELECT * FROM child_data'
You get a table with a single column and 3 numbers.
-
Merge the environment:
platform merge
-
Check the data in the production environment:
platform sql --environment main 'SELECT * FROM child_data'
You get an error message that the table doesn’t exist.
Data in parent environments
To see how the data in parent environments can be inherited, follow these steps:
-
Add a table to your production database:
platform sql --environment main 'CREATE TABLE parent_data (a int); INSERT INTO parent_data(a) VALUES (1), (2), (3);'
-
See the data in the production database:
platform sql --environment main 'SELECT * FROM parent_data'
You get a table with a single column and 3 numbers.
-
Sync the data from your
dev
environment (this means copy the data from production):platform sync data --environment dev
-
Check the data in the preview environment
platform sql --environment dev 'SELECT * FROM parent_data'
You see the same table as in step 2.
So you can test your changes with confidence in your preview environments, knowing they work in production. But you don’t have to worry about your tests affecting your production data.
What’s next
You’ve got your app up and running and connected it to a service with data. Great job!
You can end there if you want or continue to monitor your app.