function test_many_to_many_set_when_already_in_a_transaction()
 {
     $this->assertTrue(No2_SQLQuery::_beginTransaction());
     $admin = User::all()->root()->select();
     $admin->set_roles([Role::ADMIN_ID]);
     $roles = $admin->roles()->select();
     $this->assertTrue(No2_SQLQuery::_commitTransaction());
     $this->assertCount(1, $roles, 'root should have exactly one role');
     $this->assertEquals(Role::ADMIN_ID, $roles[0]->id, 'the only root role should be ADMIN');
 }
 /**
  * Links this model to the others: handles all the stuff about the join table.
  *
  * @param
  *  array $relation: The relation to use
  *
  *  Define the relation with an array containing 3 values:
  *    - The target key: the column linked to this model in the join table
  *    - The join table name
  *    - The linked key: the column linked to the other model in the join table
  *
  * @param
  *  array $others: An array of Models or of ids
  *
  * @throws
  *  InvalidArgumentException when the relation is not well defined
  *  LogicException when $this is a new record
  *
  * @return
  *  boolean: true on success, false otherwise
  */
 protected function many_to_many_set($relation, $others = null)
 {
     if ($this->is_new_record()) {
         throw new LogicException('many_to_many_set called on a new record');
     }
     if (!is_array($relation) || count($relation) < 3) {
         throw new InvalidArgumentException('The relation is not defined');
     }
     list($target_key, $join_table, $linked_key) = $relation;
     $delete = 'DELETE FROM {join_table} WHERE {target_key} = :id';
     $insert = 'INSERT INTO {join_table} ({target_key}, {linked_key}) VALUES';
     $insert_params = $delete_params = ['{join_table}' => $join_table, '{target_key}' => $target_key, '{linked_key}' => $linked_key, ':id' => $this->id];
     // build the VALUES (...) for the INSERT statment
     $values = [];
     if (is_array($others) && !empty($others)) {
         for ($i = 0, $n = count($others); $i < $n; $i++) {
             $other = $others[$i];
             $label = ":val_{$i}";
             $values[] = "(:id, {$label})";
             $insert_params[$label] = is_object($other) && isset($other->id) ? $other->id : $other;
         }
     }
     $values = join(', ', $values);
     $profile = $this->__db_profile;
     $options = ['profile' => $profile];
     if (empty($values)) {
         $success = No2_SQLQuery::execute($delete, $delete_params, $options) !== false;
     } else {
         // start a transaction if we're not already in one.
         $already_in_transaction = No2_SQLQuery::_inTransaction();
         if (!$already_in_transaction) {
             No2_SQLQuery::_beginTransaction($profile);
         }
         // do the work
         $success = No2_SQLQuery::execute($delete, $delete_params, $options) !== false && No2_SQLQuery::execute("{$insert} {$values}", $insert_params, $options) !== false;
         // terminate the transaction if we started it.
         if (!$already_in_transaction) {
             if ($success) {
                 No2_SQLQuery::_commitTransaction($profile);
             } else {
                 No2_SQLQuery::_rollBackTransaction($profile);
             }
         }
     }
     return $success;
 }
// load Composer stuff
require_once PROJECTDIR . '/vendor/autoload.php';
// initialize no2 framework.
require_once PROJECTDIR . '/no2/no2.inc.php';
// get the config stuff
require_once APPDIR . '/config.class.php';
AppConfig::parse(PROJECTDIR . '/config/config.yml', array('{{APPDIR}}' => APPDIR, '{{PROJECTDIR}}' => PROJECTDIR, '{{WEBDIR}}' => WEBDIR));
// load the application's models.
require_once APPDIR . '/models/user.class.php';
// load the application's helpers.
require_once APPDIR . '/help.inc.php';
// set the timezone
date_default_timezone_set(AppConfig::get('l10n.default_timezone'));
// set the locale
setlocale(LC_ALL, AppConfig::get('l10n.default_locale'));
// start the logger
if (!No2_Logger::setup(AppConfig::get('logger'))) {
    error_log('unable to setup Logger');
}
// connect to the database.
No2_SQLQuery::setup(AppConfig::get('database'));
// try our best to hide the fact that we still use PHP in the 21th century.
if (function_exists('header_remove')) {
    header_remove('X-Powered-By');
    // PHP 5.3+
} else {
    @ini_set('expose_php', 'off');
}
// start the session
session_set_cookie_params(0, dirname($_SERVER['SCRIPT_NAME']));
session_start() or die('session_start()');
 /**
  * Perform a DELETE instruction to remove the associated row with this
  * object in the database.
  *
  * This function doesn't check for association, it doesn't cleanup
  * dependencies, and doesn't throw an error when it fails. For all theses
  * reasons direct use of this method is highly discouraged, destroy()
  * should be called instead. As a result it is protected and prefixed by an
  * underline. A subclass that *really* want to make this method available
  * (meaning bypassing destroy()) would have to define public delete()
  * method forwarding the call to _delete():
  * @code
  *   public function delete()
  *   {
  *     return $this->_delete();
  *   }
  * @endcode
  *
  * @return
  *   false on error, true otherwise
  */
 protected function _delete()
 {
     $query = new No2_SQLQuery(get_class($this));
     $query->query_on($this->__db_profile);
     return $query->id($this->id)->delete();
 }
 /**
  * set the database profile to use for this query.
  *
  * @throw
  *   InvalidArgumentException if a bad database profile is given.
  */
 public function query_on($profile)
 {
     No2_SQLQuery::_database_or_throw($profile);
     // checking validity
     $profiled = $this->specialize();
     $profiled->profile = $profile;
     return $profiled;
 }