added example for developpers module
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
name: DBTNG Example
|
||||
type: module
|
||||
description: 'Demonstrates how to use the database API: DBTNG.'
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- drupal:user
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the dbtng_example module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*
|
||||
* Creates some default entries on this module custom table.
|
||||
*
|
||||
* @see hook_install()
|
||||
*
|
||||
* @ingroup dbtng_example
|
||||
*/
|
||||
function dbtng_example_install() {
|
||||
// Add a default entry.
|
||||
$fields = [
|
||||
'name' => 'John',
|
||||
'surname' => 'Doe',
|
||||
'age' => 0,
|
||||
];
|
||||
db_insert('dbtng_example')
|
||||
->fields($fields)
|
||||
->execute();
|
||||
|
||||
// Add another entry.
|
||||
$fields = [
|
||||
'name' => 'John',
|
||||
'surname' => 'Roe',
|
||||
'age' => 100,
|
||||
'uid' => 1,
|
||||
];
|
||||
db_insert('dbtng_example')
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*
|
||||
* Defines the database tables used by this module.
|
||||
*
|
||||
* @see hook_schema()
|
||||
*
|
||||
* @ingroup dbtng_example
|
||||
*/
|
||||
function dbtng_example_schema() {
|
||||
$schema['dbtng_example'] = [
|
||||
'description' => 'Stores example person entries for demonstration purposes.',
|
||||
'fields' => [
|
||||
'pid' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique person ID.',
|
||||
],
|
||||
'uid' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => "Creator user's {users}.uid",
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Name of the person.',
|
||||
],
|
||||
'surname' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Surname of the person.',
|
||||
],
|
||||
'age' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'The age of the person in years.',
|
||||
],
|
||||
],
|
||||
'primary key' => ['pid'],
|
||||
'indexes' => [
|
||||
'name' => ['name'],
|
||||
'surname' => ['surname'],
|
||||
'age' => ['age'],
|
||||
],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
dbtng_example.description:
|
||||
title: DBTNG Example
|
||||
route_name: dbtng_example
|
||||
expanded: TRUE
|
||||
|
||||
dbtng_example.list:
|
||||
title: List
|
||||
route_name: dbtng_list
|
||||
weight: 0
|
||||
parent: dbtng_example.description
|
||||
dbtng_example.add:
|
||||
title: Add entry
|
||||
route_name: dbtng_add
|
||||
weight: 1
|
||||
parent: dbtng_example.description
|
||||
dbtng_example.update:
|
||||
title: Update entry
|
||||
route_name: dbtng_update
|
||||
weight: 2
|
||||
parent: dbtng_example.description
|
||||
dbtng_example.advanced:
|
||||
title: Advanced list
|
||||
route_name: dbtng_advanced
|
||||
weight: 3
|
||||
parent: dbtng_example.description
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This is an example outlining how a module can use the DBTNG database API.
|
||||
*
|
||||
* @todo Demonstrate transaction usage.
|
||||
*
|
||||
* General documentation is available at
|
||||
* @link database Database abstraction layer documentation @endlink and
|
||||
* at @link http://drupal.org/node/310069 @endlink.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup dbtng_example Example: Database (DBTNG)
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Database examples, including DBTNG.
|
||||
*
|
||||
* 'DBTNG' means 'Database: The Next Generation.' Yes, Drupallers are nerds.
|
||||
*
|
||||
* General documentation is available at
|
||||
* @link database.inc database abstraction layer documentation @endlink and
|
||||
* at @link http://drupal.org/node/310069 Database API @endlink.
|
||||
*
|
||||
* The several examples in DbtngExampleController (see
|
||||
* /src/Drupal/dbtng_example/Controller/DbtngExampleController.php) demonstrate
|
||||
* basic database usage.
|
||||
*
|
||||
* db_insert() example:
|
||||
* @code
|
||||
* // INSERT INTO {dbtng_example} (name, surname) VALUES('John, 'Doe')
|
||||
* db_insert('dbtng_example')
|
||||
* ->fields(array('name' => 'John', 'surname' => 'Doe'))
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* db_update() example:
|
||||
* @code
|
||||
* // UPDATE {dbtng_example} SET name = 'Jane' WHERE name = 'John'
|
||||
* db_update('dbtng_example')
|
||||
* ->fields(array('name' => 'Jane'))
|
||||
* ->condition('name', 'John')
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* db_delete() example:
|
||||
* @code
|
||||
* // DELETE FROM {dbtng_example} WHERE name = 'Jane'
|
||||
* db_delete('dbtng_example')
|
||||
* ->condition('name', 'Jane')
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* See @link database Database Abstraction Layer @endlink
|
||||
* @see db_insert()
|
||||
* @see db_update()
|
||||
* @see db_delete()
|
||||
*/
|
||||
|
||||
/**
|
||||
* @} End of "defgroup dbtng_example".
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
dbtng_example:
|
||||
path: 'examples/dbtng-example'
|
||||
defaults:
|
||||
_title: 'DBTNG Example'
|
||||
_controller: '\Drupal\dbtng_example\Controller\DbtngExampleController::entryList'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
dbtng_list:
|
||||
path: 'examples/dbtng-example/list'
|
||||
defaults:
|
||||
_title: 'List'
|
||||
_controller: '\Drupal\dbtng_example\Controller\DbtngExampleController::entryList'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
dbtng_add:
|
||||
path: 'examples/dbtng-example/add'
|
||||
defaults:
|
||||
_title: 'Add entry'
|
||||
_form: '\Drupal\dbtng_example\Form\DbtngExampleAddForm'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
dbtng_update:
|
||||
path: 'examples/dbtng-example/update'
|
||||
defaults:
|
||||
_title: 'Update entry'
|
||||
_form: '\Drupal\dbtng_example\Form\DbtngExampleUpdateForm'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
dbtng_advanced:
|
||||
path: 'examples/dbtng-example/advanced'
|
||||
defaults:
|
||||
_title: 'Advanced list'
|
||||
_controller: '\Drupal\dbtng_example\Controller\DbtngExampleController::entryAdvancedList'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\dbtng_example\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\dbtng_example\DbtngExampleStorage;
|
||||
|
||||
/**
|
||||
* Controller for DBTNG Example.
|
||||
*/
|
||||
class DbtngExampleController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Render a list of entries in the database.
|
||||
*/
|
||||
public function entryList() {
|
||||
$content = [];
|
||||
|
||||
$content['message'] = [
|
||||
'#markup' => $this->t('Generate a list of all entries in the database. There is no filter in the query.'),
|
||||
];
|
||||
|
||||
$rows = [];
|
||||
$headers = [t('Id'), t('uid'), t('Name'), t('Surname'), t('Age')];
|
||||
|
||||
foreach ($entries = DbtngExampleStorage::load() as $entry) {
|
||||
// Sanitize each entry.
|
||||
$rows[] = array_map('Drupal\Component\Utility\SafeMarkup::checkPlain', (array) $entry);
|
||||
}
|
||||
$content['table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $headers,
|
||||
'#rows' => $rows,
|
||||
'#empty' => t('No entries available.'),
|
||||
];
|
||||
// Don't cache this page.
|
||||
$content['#cache']['max-age'] = 0;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a filtered list of entries in the database.
|
||||
*/
|
||||
public function entryAdvancedList() {
|
||||
$content = [];
|
||||
|
||||
$content['message'] = [
|
||||
'#markup' => $this->t('A more complex list of entries in the database.') . ' ' .
|
||||
$this->t('Only the entries with name = "John" and age older than 18 years are shown, the username of the person who created the entry is also shown.'),
|
||||
];
|
||||
|
||||
$headers = [
|
||||
t('Id'),
|
||||
t('Created by'),
|
||||
t('Name'),
|
||||
t('Surname'),
|
||||
t('Age'),
|
||||
];
|
||||
|
||||
$rows = [];
|
||||
foreach ($entries = DbtngExampleStorage::advancedLoad() as $entry) {
|
||||
// Sanitize each entry.
|
||||
$rows[] = array_map('Drupal\Component\Utility\SafeMarkup::checkPlain', $entry);
|
||||
}
|
||||
$content['table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $headers,
|
||||
'#rows' => $rows,
|
||||
'#attributes' => ['id' => 'dbtng-example-advanced-list'],
|
||||
'#empty' => t('No entries available.'),
|
||||
];
|
||||
// Don't cache this page.
|
||||
$content['#cache']['max-age'] = 0;
|
||||
return $content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\dbtng_example;
|
||||
|
||||
/**
|
||||
* Class DbtngExampleStorage.
|
||||
*/
|
||||
class DbtngExampleStorage {
|
||||
|
||||
/**
|
||||
* Save an entry in the database.
|
||||
*
|
||||
* The underlying DBTNG function is db_insert().
|
||||
*
|
||||
* Exception handling is shown in this example. It could be simplified
|
||||
* without the try/catch blocks, but since an insert will throw an exception
|
||||
* and terminate your application if the exception is not handled, it is best
|
||||
* to employ try/catch.
|
||||
*
|
||||
* @param array $entry
|
||||
* An array containing all the fields of the database record.
|
||||
*
|
||||
* @return int
|
||||
* The number of updated rows.
|
||||
*
|
||||
* @throws \Exception
|
||||
* When the database insert fails.
|
||||
*
|
||||
* @see db_insert()
|
||||
*/
|
||||
public static function insert(array $entry) {
|
||||
$return_value = NULL;
|
||||
try {
|
||||
$return_value = db_insert('dbtng_example')
|
||||
->fields($entry)
|
||||
->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
drupal_set_message(t('db_insert failed. Message = %message, query= %query', [
|
||||
'%message' => $e->getMessage(),
|
||||
'%query' => $e->query_string,
|
||||
]
|
||||
), 'error');
|
||||
}
|
||||
return $return_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an entry in the database.
|
||||
*
|
||||
* @param array $entry
|
||||
* An array containing all the fields of the item to be updated.
|
||||
*
|
||||
* @return int
|
||||
* The number of updated rows.
|
||||
*
|
||||
* @see db_update()
|
||||
*/
|
||||
public static function update(array $entry) {
|
||||
try {
|
||||
// db_update()...->execute() returns the number of rows updated.
|
||||
$count = db_update('dbtng_example')
|
||||
->fields($entry)
|
||||
->condition('pid', $entry['pid'])
|
||||
->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
drupal_set_message(t('db_update failed. Message = %message, query= %query', [
|
||||
'%message' => $e->getMessage(),
|
||||
'%query' => $e->query_string,
|
||||
]
|
||||
), 'error');
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an entry from the database.
|
||||
*
|
||||
* @param array $entry
|
||||
* An array containing at least the person identifier 'pid' element of the
|
||||
* entry to delete.
|
||||
*
|
||||
* @see db_delete()
|
||||
*/
|
||||
public static function delete(array $entry) {
|
||||
db_delete('dbtng_example')
|
||||
->condition('pid', $entry['pid'])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the database using a filter array.
|
||||
*
|
||||
* The standard function to perform reads was db_query(), and for static
|
||||
* queries, it still is.
|
||||
*
|
||||
* db_query() used an SQL query with placeholders and arguments as parameters.
|
||||
*
|
||||
* Drupal DBTNG provides an abstracted interface that will work with a wide
|
||||
* variety of database engines.
|
||||
*
|
||||
* db_query() is deprecated except when doing a static query. The following is
|
||||
* perfectly acceptable in Drupal 8. See
|
||||
* @link http://drupal.org/node/310072 the handbook page on static queries @endlink
|
||||
*
|
||||
* @code
|
||||
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
|
||||
* db_query(
|
||||
* "SELECT * FROM {dbtng_example} WHERE uid = :uid and name = :name",
|
||||
* array(':uid' => 0, ':name' => 'John')
|
||||
* )->execute();
|
||||
* @endcode
|
||||
*
|
||||
* But for more dynamic queries, Drupal provides the db_select()
|
||||
* API method, so there are several ways to perform the same SQL query. See
|
||||
* the
|
||||
* @link http://drupal.org/node/310075 handbook page on dynamic queries. @endlink
|
||||
* @code
|
||||
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
|
||||
* db_select('dbtng_example')
|
||||
* ->fields('dbtng_example')
|
||||
* ->condition('uid', 0)
|
||||
* ->condition('name', 'John')
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* Here is db_select with named placeholders:
|
||||
* @code
|
||||
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
|
||||
* $arguments = array(':name' => 'John', ':uid' => 0);
|
||||
* db_select('dbtng_example')
|
||||
* ->fields('dbtng_example')
|
||||
* ->where('uid = :uid AND name = :name', $arguments)
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* Conditions are stacked and evaluated as AND and OR depending on the type of
|
||||
* query. For more information, read the conditional queries handbook page at:
|
||||
* http://drupal.org/node/310086
|
||||
*
|
||||
* The condition argument is an 'equal' evaluation by default, but this can be
|
||||
* altered:
|
||||
* @code
|
||||
* // SELECT * FROM {dbtng_example} WHERE age > 18
|
||||
* db_select('dbtng_example')
|
||||
* ->fields('dbtng_example')
|
||||
* ->condition('age', 18, '>')
|
||||
* ->execute();
|
||||
* @endcode
|
||||
*
|
||||
* @param array $entry
|
||||
* An array containing all the fields used to search the entries in the
|
||||
* table.
|
||||
*
|
||||
* @return object
|
||||
* An object containing the loaded entries if found.
|
||||
*
|
||||
* @see db_select()
|
||||
* @see db_query()
|
||||
* @see http://drupal.org/node/310072
|
||||
* @see http://drupal.org/node/310075
|
||||
*/
|
||||
public static function load(array $entry = []) {
|
||||
// Read all fields from the dbtng_example table.
|
||||
$select = db_select('dbtng_example', 'example');
|
||||
$select->fields('example');
|
||||
|
||||
// Add each field and value as a condition to this query.
|
||||
foreach ($entry as $field => $value) {
|
||||
$select->condition($field, $value);
|
||||
}
|
||||
// Return the result in object format.
|
||||
return $select->execute()->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dbtng_example records joined with user records.
|
||||
*
|
||||
* DBTNG also helps processing queries that return several rows, providing the
|
||||
* found objects in the same query execution call.
|
||||
*
|
||||
* This function queries the database using a JOIN between users table and the
|
||||
* example entries, to provide the username that created the entry, and
|
||||
* creates a table with the results, processing each row.
|
||||
*
|
||||
* SELECT
|
||||
* e.pid as pid, e.name as name, e.surname as surname, e.age as age
|
||||
* u.name as username
|
||||
* FROM
|
||||
* {dbtng_example} e
|
||||
* JOIN
|
||||
* users u ON e.uid = u.uid
|
||||
* WHERE
|
||||
* e.name = 'John' AND e.age > 18
|
||||
*
|
||||
* @see db_select()
|
||||
* @see http://drupal.org/node/310075
|
||||
*/
|
||||
public static function advancedLoad() {
|
||||
$select = db_select('dbtng_example', 'e');
|
||||
// Join the users table, so we can get the entry creator's username.
|
||||
$select->join('users_field_data', 'u', 'e.uid = u.uid');
|
||||
// Select these specific fields for the output.
|
||||
$select->addField('e', 'pid');
|
||||
$select->addField('u', 'name', 'username');
|
||||
$select->addField('e', 'name');
|
||||
$select->addField('e', 'surname');
|
||||
$select->addField('e', 'age');
|
||||
// Filter only persons named "John".
|
||||
$select->condition('e.name', 'John');
|
||||
// Filter only persons older than 18 years.
|
||||
$select->condition('e.age', 18, '>');
|
||||
// Make sure we only get items 0-49, for scalability reasons.
|
||||
$select->range(0, 50);
|
||||
|
||||
$entries = $select->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\dbtng_example\Form;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\dbtng_example\DbtngExampleStorage;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form to add a database entry, with all the interesting fields.
|
||||
*/
|
||||
class DbtngExampleAddForm implements FormInterface, ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* We'll need this service in order to check if the user is logged in.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* We'll use the ContainerInjectionInterface pattern here to inject the
|
||||
* current user and also get the string_translation service.
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$form = new static(
|
||||
$container->get('current_user')
|
||||
);
|
||||
// The StringTranslationTrait trait manages the string translation service
|
||||
// for us. We can inject the service here.
|
||||
$form->setStringTranslation($container->get('string_translation'));
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the new form object.
|
||||
*/
|
||||
public function __construct(AccountProxyInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'dbtng_add_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = [];
|
||||
|
||||
$form['message'] = [
|
||||
'#markup' => $this->t('Add an entry to the dbtng_example table.'),
|
||||
];
|
||||
|
||||
$form['add'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Add a person entry'),
|
||||
];
|
||||
$form['add']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#size' => 15,
|
||||
];
|
||||
$form['add']['surname'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Surname'),
|
||||
'#size' => 15,
|
||||
];
|
||||
$form['add']['age'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Age'),
|
||||
'#size' => 5,
|
||||
'#description' => $this->t("Values greater than 127 will cause an exception. Try it - it's a great example why exception handling is needed with DTBNG."),
|
||||
];
|
||||
$form['add']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// Verify that the user is logged-in.
|
||||
if ($this->currentUser->isAnonymous()) {
|
||||
$form_state->setError($form['add'], $this->t('You must be logged in to add values to the database.'));
|
||||
}
|
||||
// Confirm that age is numeric.
|
||||
if (!intval($form_state->getValue('age'))) {
|
||||
$form_state->setErrorByName('age', $this->t('Age needs to be a number'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Gather the current user so the new record has ownership.
|
||||
$account = $this->currentUser;
|
||||
// Save the submitted entry.
|
||||
$entry = [
|
||||
'name' => $form_state->getValue('name'),
|
||||
'surname' => $form_state->getValue('surname'),
|
||||
'age' => $form_state->getValue('age'),
|
||||
'uid' => $account->id(),
|
||||
];
|
||||
$return = DbtngExampleStorage::insert($entry);
|
||||
if ($return) {
|
||||
drupal_set_message($this->t('Created entry @entry', ['@entry' => print_r($entry, TRUE)]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\dbtng_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\dbtng_example\DbtngExampleStorage;
|
||||
|
||||
/**
|
||||
* Sample UI to update a record.
|
||||
*/
|
||||
class DbtngExampleUpdateForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'dbtng_update_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample UI to update a record.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Wrap the form in a div.
|
||||
$form = [
|
||||
'#prefix' => '<div id="updateform">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
// Add some explanatory text to the form.
|
||||
$form['message'] = [
|
||||
'#markup' => $this->t('Demonstrates a database update operation.'),
|
||||
];
|
||||
// Query for items to display.
|
||||
$entries = DbtngExampleStorage::load();
|
||||
// Tell the user if there is nothing to display.
|
||||
if (empty($entries)) {
|
||||
$form['no_values'] = [
|
||||
'#value' => t('No entries exist in the table dbtng_example table.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
$keyed_entries = [];
|
||||
foreach ($entries as $entry) {
|
||||
$options[$entry->pid] = t('@pid: @name @surname (@age)', [
|
||||
'@pid' => $entry->pid,
|
||||
'@name' => $entry->name,
|
||||
'@surname' => $entry->surname,
|
||||
'@age' => $entry->age,
|
||||
]);
|
||||
$keyed_entries[$entry->pid] = $entry;
|
||||
}
|
||||
|
||||
// Grab the pid.
|
||||
$pid = $form_state->getValue('pid');
|
||||
// Use the pid to set the default entry for updating.
|
||||
$default_entry = !empty($pid) ? $keyed_entries[$pid] : $entries[0];
|
||||
|
||||
// Save the entries into the $form_state. We do this so the AJAX callback
|
||||
// doesn't need to repeat the query.
|
||||
$form_state->setValue('entries', $keyed_entries);
|
||||
|
||||
$form['pid'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
'#title' => t('Choose entry to update'),
|
||||
'#default_value' => $default_entry->pid,
|
||||
'#ajax' => [
|
||||
'wrapper' => 'updateform',
|
||||
'callback' => [$this, 'updateCallback'],
|
||||
],
|
||||
];
|
||||
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Updated first name'),
|
||||
'#size' => 15,
|
||||
'#default_value' => $default_entry->name,
|
||||
];
|
||||
|
||||
$form['surname'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Updated last name'),
|
||||
'#size' => 15,
|
||||
'#default_value' => $default_entry->surname,
|
||||
];
|
||||
$form['age'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Updated age'),
|
||||
'#size' => 4,
|
||||
'#default_value' => $default_entry->age,
|
||||
'#description' => t('Values greater than 127 will cause an exception'),
|
||||
];
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Update'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback handler for the pid select.
|
||||
*
|
||||
* When the pid changes, populates the defaults from the database in the form.
|
||||
*/
|
||||
public function updateCallback(array $form, FormStateInterface $form_state) {
|
||||
// Gather the DB results from $form_state.
|
||||
$entries = $form_state->getValue('entries');
|
||||
// Use the specific entry for this $form_state.
|
||||
$entry = $entries[$form_state->getValue('pid')];
|
||||
// Setting the #value of items is the only way I was able to figure out
|
||||
// to get replaced defaults on these items. #default_value will not do it
|
||||
// and shouldn't.
|
||||
foreach (['name', 'surname', 'age'] as $item) {
|
||||
$form[$item]['#value'] = $entry->$item;
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// Confirm that age is numeric.
|
||||
if (!intval($form_state->getValue('age'))) {
|
||||
$form_state->setErrorByName('age', t('Age needs to be a number'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Gather the current user so the new record has ownership.
|
||||
$account = $this->currentUser();
|
||||
// Save the submitted entry.
|
||||
$entry = [
|
||||
'pid' => $form_state->getValue('pid'),
|
||||
'name' => $form_state->getValue('name'),
|
||||
'surname' => $form_state->getValue('surname'),
|
||||
'age' => $form_state->getValue('age'),
|
||||
'uid' => $account->id(),
|
||||
];
|
||||
$count = DbtngExampleStorage::update($entry);
|
||||
drupal_set_message(t('Updated entry @entry (@count row updated)', [
|
||||
'@count' => $count,
|
||||
'@entry' => print_r($entry, TRUE),
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\dbtng_example\Functional;
|
||||
|
||||
use Drupal\dbtng_example\DbtngExampleStorage;
|
||||
use Drupal\Tests\examples\Functional\ExamplesBrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests for the dbtng_example module.
|
||||
*
|
||||
* @group dbtng_example
|
||||
* @group examples
|
||||
*
|
||||
* @ingroup dbtng_example
|
||||
*/
|
||||
class DbtngExampleTest extends ExamplesBrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['dbtng_example'];
|
||||
|
||||
/**
|
||||
* The installation profile to use with this test.
|
||||
*
|
||||
* We need the 'minimal' profile in order to make sure the Tool block is
|
||||
* available.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* Regression test for dbtng_example.
|
||||
*
|
||||
* We'll verify the following:
|
||||
* - Assert that two entries were inserted at install.
|
||||
* - Test the example description page.
|
||||
* - Verify that the example pages have links in the Tools menu.
|
||||
*/
|
||||
public function testDbtngExample() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Assert that two entries were inserted at install.
|
||||
$result = DbtngExampleStorage::load();
|
||||
$this->assertCount(2, $result, 'Did not find two entries in the table after installing the module.');
|
||||
|
||||
// Test the example description page.
|
||||
$this->drupalGet('/examples/dbtng-example');
|
||||
$assert->statusCodeEquals(200);
|
||||
|
||||
// Verify and validate that default menu links were loaded for this module.
|
||||
$links = $this->providerMenuLinks();
|
||||
foreach ($links as $page => $hrefs) {
|
||||
foreach ($hrefs as $href) {
|
||||
$this->drupalGet($page);
|
||||
$assert->linkByHrefExists($href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing menu links.
|
||||
*
|
||||
* @return array
|
||||
* Array of page -> link relationships to check for:
|
||||
* - The key is the path to the page where our link should appear.
|
||||
* - The value is an array of links that should appear on that page.
|
||||
*/
|
||||
protected function providerMenuLinks() {
|
||||
return [
|
||||
'' => [
|
||||
'/examples/dbtng-example',
|
||||
],
|
||||
'/examples/dbtng-example' => [
|
||||
'/examples/dbtng-example/add',
|
||||
'/examples/dbtng-example/update',
|
||||
'/examples/dbtng-example/advanced',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the UI.
|
||||
*/
|
||||
public function testUi() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$this->drupalLogin($this->createUser());
|
||||
// Test the basic list.
|
||||
$this->drupalGet('/examples/dbtng-example');
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->pageTextMatches('%John[td/<>\w\s]+Doe%');
|
||||
|
||||
// Test the add tab.
|
||||
// Add the new entry.
|
||||
$this->drupalPostForm(
|
||||
'/examples/dbtng-example/add',
|
||||
[
|
||||
'name' => 'Some',
|
||||
'surname' => 'Anonymous',
|
||||
'age' => 33,
|
||||
],
|
||||
'Add'
|
||||
);
|
||||
// Now find the new entry.
|
||||
$this->drupalGet('/examples/dbtng-example');
|
||||
$assert->pageTextMatches('%Some[td/<>\w\s]+Anonymous%');
|
||||
// Try the update tab.
|
||||
// Find out the pid of our "anonymous" guy.
|
||||
$result = DbtngExampleStorage::load(['surname' => 'Anonymous']);
|
||||
$this->drupalGet('/examples/dbtng-example');
|
||||
$this->assertCount(1, $result, 'Did not find one entry in the table with surname = "Anonymous".');
|
||||
$entry = $result[0];
|
||||
unset($entry->uid);
|
||||
|
||||
$entry = ['name' => 'NewFirstName', 'age' => 22];
|
||||
$this->drupalPostForm('/examples/dbtng-example/update', $entry, 'Update');
|
||||
// Now find the new entry.
|
||||
$this->drupalGet('/examples/dbtng-example');
|
||||
$assert->pageTextMatches('%NewFirstName[td/<>\w\s]+Anonymous%');
|
||||
|
||||
// Try the advanced tab.
|
||||
$this->drupalGet('/examples/dbtng-example/advanced');
|
||||
$rows = $this->xpath("//*[@id='dbtng-example-advanced-list'][1]/tbody/tr");
|
||||
$this->assertCount(1, $rows);
|
||||
|
||||
$field = $this->xpath("//*[@id='dbtng-example-advanced-list'][1]/tbody/tr/td[4]");
|
||||
$this->assertEquals('Roe', $field[0]->getText());
|
||||
|
||||
// Try to add an entry while logged out.
|
||||
$this->drupalLogout();
|
||||
$this->drupalPostForm(
|
||||
'/examples/dbtng-example/add',
|
||||
[
|
||||
'name' => 'Anonymous',
|
||||
'surname' => 'UserCannotPost',
|
||||
'age' => 'not a number',
|
||||
],
|
||||
'Add'
|
||||
);
|
||||
$assert->pageTextContains('You must be logged in to add values to the database.');
|
||||
$assert->pageTextContains('Age needs to be a number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests several combinations, adding entries, updating and deleting.
|
||||
*/
|
||||
public function testDbtngExampleStorage() {
|
||||
// Create a new entry.
|
||||
$entry = [
|
||||
'name' => 'James',
|
||||
'surname' => 'Doe',
|
||||
'age' => 23,
|
||||
];
|
||||
DbtngExampleStorage::insert($entry);
|
||||
|
||||
// Save another entry.
|
||||
$entry = [
|
||||
'name' => 'Jane',
|
||||
'surname' => 'NotDoe',
|
||||
'age' => 19,
|
||||
];
|
||||
DbtngExampleStorage::insert($entry);
|
||||
|
||||
// Verify that 4 records are found in the database.
|
||||
$result = DbtngExampleStorage::load();
|
||||
$this->assertCount(4, $result);
|
||||
|
||||
// Verify 2 of these records have 'Doe' as surname.
|
||||
$result = DbtngExampleStorage::load(['surname' => 'Doe']);
|
||||
$this->assertCount(2, $result, 'Did not find two entries in the table with surname = "Doe".');
|
||||
|
||||
// Now find our not-Doe entry.
|
||||
$result = DbtngExampleStorage::load(['surname' => 'NotDoe']);
|
||||
// Found one entry in the table with surname "NotDoe'.
|
||||
$this->assertCount(1, $result, 'Did not find one entry in the table with surname "NotDoe');
|
||||
// Our NotDoe will be changed to "NowDoe".
|
||||
$entry = $result[0];
|
||||
$entry->surname = "NowDoe";
|
||||
// update() returns the number of entries updated.
|
||||
$this->assertNotEquals(DbtngExampleStorage::update((array) $entry), 0);
|
||||
|
||||
$result = DbtngExampleStorage::load(['surname' => 'NowDoe']);
|
||||
$this->assertCount(1, $result, "Did not find renamed 'NowDoe' surname.");
|
||||
|
||||
// Read only John Doe entry.
|
||||
$result = DbtngExampleStorage::load(['name' => 'John', 'surname' => 'Doe']);
|
||||
$this->assertCount(1, $result, 'Did not find one entry for John Doe.');
|
||||
|
||||
// Get the entry.
|
||||
$entry = (array) end($result);
|
||||
// Change age to 45.
|
||||
$entry['age'] = 45;
|
||||
// Update entry in database.
|
||||
DbtngExampleStorage::update((array) $entry);
|
||||
|
||||
// Find entries with age = 45.
|
||||
// Read only John Doe entry.
|
||||
$result = DbtngExampleStorage::load(['surname' => 'NowDoe']);
|
||||
// Found one entry with surname = Nowdoe.
|
||||
$this->assertCount(1, $result, 'Did not find one entry with surname = Nowdoe.');
|
||||
|
||||
// Verify it is Jane NowDoe.
|
||||
$entry = (array) end($result);
|
||||
// The name Jane is found in the entry.
|
||||
$this->assertEquals('Jane', $entry['name'], 'The name Jane is not found in the entry.');
|
||||
// The surname NowDoe is found in the entry.
|
||||
$this->assertEquals('NowDoe', $entry['surname'], 'The surname NowDoe is not found in the entry.');
|
||||
|
||||
// Delete the entry.
|
||||
DbtngExampleStorage::delete($entry);
|
||||
|
||||
// Verify that now there are only 3 records.
|
||||
$result = DbtngExampleStorage::load();
|
||||
// Found only three records, a record was deleted.
|
||||
$this->assertCount(3, $result, 'Did not find only three records, a record might not have been deleted.');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user