333 lines
9.8 KiB
PHP
333 lines
9.8 KiB
PHP
<?php
|
|
/**
|
|
* Created by PhpStorm.
|
|
* User: Exodus
|
|
* Date: 19.12.2015
|
|
* Time: 12:47
|
|
*
|
|
* This is an "on top" extension for "Schema Builder"
|
|
* see: https://github.com/ikkez/f3-schema-builder
|
|
*
|
|
* Features:
|
|
* - FOREIGN KEY CONSTRAINTS (single column key)
|
|
*/
|
|
|
|
namespace DB\SQL\MySQL;
|
|
use DB\SQL;
|
|
|
|
class TableModifier extends SQL\TableModifier {
|
|
|
|
const TEXT_ConstraintNotValid = 'Constraint `%s` is not valid';
|
|
|
|
|
|
/**
|
|
* return table foreign key constraints as assoc array
|
|
* -> if §constraint is passed, constraints are limited to that column
|
|
* @param null| \DB\SQL\MySQL\Constraint $constraint
|
|
* @return Constraint[]
|
|
*/
|
|
public function listConstraint($constraint = null){
|
|
|
|
$constraintName = '%';
|
|
$keys = [];
|
|
if($constraint instanceof Constraint){
|
|
// list constraints for given column in this table
|
|
$constraintName = $constraint->getConstraintName() . '%';
|
|
$keys = $constraint->getKeys();
|
|
}
|
|
|
|
$this->db->exec("USE information_schema");
|
|
$constraintsData = $this->db->exec("
|
|
SELECT
|
|
*
|
|
FROM
|
|
referential_constraints
|
|
WHERE
|
|
constraint_schema = :db AND
|
|
table_name = :table AND
|
|
constraint_name LIKE :constraint_name
|
|
", [
|
|
':db' => $this->db->name(),
|
|
':table' => $this->name,
|
|
':constraint_name' => $constraintName
|
|
]);
|
|
// switch back to current DB
|
|
$this->db->exec("USE " . $this->db->name());
|
|
|
|
$constraints = [];
|
|
foreach($constraintsData as $data){
|
|
$constraints[$data['CONSTRAINT_NAME']] = new Constraint($this, $keys, $data['REFERENCED_TABLE_NAME'] );
|
|
}
|
|
|
|
return $constraints;
|
|
}
|
|
|
|
/**
|
|
* checks whether a constraint name exists or not
|
|
* -> does not check constraint params
|
|
* @param \DB\SQL\MySQL\Constraint $constraint
|
|
* @return bool
|
|
*/
|
|
public function constraintExists($constraint){
|
|
$constraints = $this->listConstraint();
|
|
return array_key_exists($constraint->getConstraintName(), $constraints);
|
|
}
|
|
|
|
/**
|
|
* drop foreign key constraint
|
|
* @param \DB\SQL\MySQL\Constraint $constraint
|
|
*/
|
|
public function dropConstraint($constraint){
|
|
if($constraint->isValid()){
|
|
$this->queries[] = "ALTER TABLE " . $this->db->quotekey($this->name) . "
|
|
DROP FOREIGN KEY " . $this->db->quotekey($constraint->getConstraintName());
|
|
}else{
|
|
trigger_error(sprintf(self::TEXT_ConstraintNotValid, 'table: ' . $this->name . ' constraintName: ' . $constraint->getConstraintName()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add/Update foreign key constraint
|
|
* @param \DB\SQL\MySQL\Constraint $constraint
|
|
*/
|
|
public function addConstraint($constraint){
|
|
|
|
if($constraint->isValid()){
|
|
if($this->constraintExists($constraint)){
|
|
// drop constraint and re-add in case something has changed
|
|
$this->dropConstraint($constraint);
|
|
}
|
|
|
|
$this->queries[] = "
|
|
ALTER TABLE " . $this->db->quotekey($this->name) . "
|
|
ADD CONSTRAINT " . $this->db->quotekey($constraint->getConstraintName()) . "
|
|
FOREIGN KEY (" . implode(', ', $constraint->getKeys()) . ")
|
|
REFERENCES " . $this->db->quotekey($constraint->getReferencedTable()) . " (" . implode(', ', $constraint->getReferencedCols()) . ")
|
|
ON DELETE " . $constraint->getOnDelete() . "
|
|
ON UPDATE " . $constraint->getOnUpdate();
|
|
}else{
|
|
trigger_error(sprintf(self::TEXT_ConstraintNotValid, 'table: ' . $this->name . ' constraintName: ' . $constraint->getConstraintName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class Column
|
|
* @package DB\SQL\MySQL
|
|
*/
|
|
class Column extends SQL\Column {
|
|
|
|
const TEXT_TableNameMissing = 'Table name missing for FOREIGN KEY in `%s`';
|
|
|
|
/**
|
|
* ass constraint to this column
|
|
* @param Constraint $constraint
|
|
*/
|
|
public function addConstraint(Constraint $constraint){
|
|
$this->table->addConstraint($constraint);
|
|
}
|
|
|
|
/**
|
|
* @see \DB\SQL\MySQL\TableModifier->constraintExists();
|
|
* @param Constraint $constraint
|
|
* @return mixed
|
|
*/
|
|
public function constraintExists(Constraint $constraint){
|
|
return $this->table->constraintExists($constraint);
|
|
}
|
|
|
|
/**
|
|
* get a new column based constraint
|
|
* $constraintData['table'] => referenceTable name (required)
|
|
* $constraintData['id'] => referenceColumns (optional) default: ['id']
|
|
* $constraintData['on-delete'] => ON DELETE action (optional) default: see \DB\SQL\MySQL\Constraint const
|
|
* $constraintData['on-update'] => ON UPDATE action (optional) default: see \DB\SQL\MySQL\Constraint const
|
|
*
|
|
* @param array $constraintData
|
|
* @return \DB\SQL\MySQL\Constraint
|
|
*/
|
|
public function newConstraint($constraintData){
|
|
|
|
$constraint = null;
|
|
|
|
if(isset($constraintData['table'])){
|
|
if(isset($constraintData['column'])){
|
|
$constraintData['column'] = (array)$constraintData['column'];
|
|
}else{
|
|
$constraintData['column'] = ['id'];
|
|
}
|
|
|
|
$constraint = new Constraint($this->table, $this->name, $constraintData['table'], $constraintData['column']);
|
|
|
|
if(isset($constraintData['on-delete'])){
|
|
$constraint->setOnDelete($constraintData['on-delete']);
|
|
}
|
|
|
|
if(isset($constraintData['on-update'])){
|
|
$constraint->setOnUpdate($constraintData['on-update']);
|
|
}
|
|
|
|
}else{
|
|
trigger_error(sprintf(self::TEXT_TableNameMissing, $this->table->name . '->' . $this->name));
|
|
}
|
|
|
|
return $constraint;
|
|
}
|
|
}
|
|
|
|
class Constraint {
|
|
|
|
// available actions
|
|
const ACTIONS_DELETE = ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'];
|
|
const ACTIONS_UPDATE = ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'];
|
|
|
|
// default actions
|
|
const ACTION_DELETE = 'RESTRICT';
|
|
const ACTION_UPDATE = 'RESTRICT';
|
|
|
|
const TEXT_ActionNotSupported = 'Constraint action `%s` is not supported.';
|
|
|
|
protected $table;
|
|
protected $keys = [];
|
|
protected $referencedTable = '';
|
|
protected $referencedCols = [];
|
|
protected $onDelete = self::ACTION_DELETE;
|
|
protected $onUpdate = self::ACTION_UPDATE;
|
|
|
|
/**
|
|
* Constraint constructor.
|
|
* @param SQL\TableBuilder $table
|
|
* @param array $keys
|
|
* @param string $referencedTable
|
|
* @param array $referencedCols
|
|
*/
|
|
public function __construct(SQL\TableBuilder $table, $keys = [], $referencedTable = '', $referencedCols = ['id']){
|
|
$this->table = &$table;
|
|
$this->setKeys($keys);
|
|
$this->setReferencedTable($referencedTable);
|
|
$this->setReferencedCols($referencedCols);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $keys
|
|
*/
|
|
public function setKeys($keys){
|
|
$this->keys = (array)$keys;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $referencedTable
|
|
*/
|
|
public function setReferencedTable($referencedTable){
|
|
$this->referencedTable = $referencedTable;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $referencedCols
|
|
*/
|
|
public function setReferencedCols($referencedCols){
|
|
$this->referencedCols = (array)$referencedCols;
|
|
}
|
|
|
|
/**
|
|
* @param string $onDelete
|
|
*/
|
|
public function setOnDelete($onDelete){
|
|
if( in_array($onDelete, self::ACTIONS_DELETE) ){
|
|
$this->onDelete = $onDelete;
|
|
}else{
|
|
trigger_error(sprintf(self::TEXT_ActionNotSupported, $onDelete));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $onUpdate
|
|
*/
|
|
public function setOnUpdate($onUpdate){
|
|
if( in_array($onUpdate, self::ACTIONS_UPDATE) ){
|
|
$this->onUpdate = $onUpdate;
|
|
}else{
|
|
trigger_error(sprintf(self::TEXT_ActionNotSupported, $onUpdate));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getKeys(){
|
|
return $this->keys;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getReferencedTable(){
|
|
return $this->referencedTable;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getReferencedCols(){
|
|
return $this->referencedCols;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getOnDelete(){
|
|
return $this->onDelete;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getOnUpdate(){
|
|
return $this->onUpdate;
|
|
}
|
|
|
|
/**
|
|
* get a constraint name for this table.
|
|
* This can either be used to generate unique constraint names for foreign keys in parent tables
|
|
* or generate a "part" of a name. e.g. for db-Query all constraints of this table (ignore columns)
|
|
* by "LIKE" selecting "information_schema"
|
|
* -> To get a certain constraint or generate a unique constraint, ALL params are required!
|
|
* @return string
|
|
*/
|
|
public function getConstraintName(){
|
|
$constraintName = 'fk_' . $this->table->name;
|
|
|
|
if(!empty($this->getKeys())){
|
|
$constraintName .= '___' . implode('__', $this->getKeys());
|
|
if(!empty($this->getReferencedTable())){
|
|
$constraintName .= '___' . $this->getReferencedTable();
|
|
if(!empty($this->getReferencedCols())){
|
|
$constraintName .= '___' . implode('__', $this->getReferencedCols());
|
|
}
|
|
}
|
|
}
|
|
|
|
return $constraintName;
|
|
}
|
|
|
|
/**
|
|
* checks if constraint is valid
|
|
* -> all required members must be set!
|
|
* @return bool
|
|
*/
|
|
public function isValid(){
|
|
$valid = false;
|
|
|
|
if(
|
|
!empty($this->getKeys()) &&
|
|
!empty($this->getReferencedTable()) &&
|
|
!empty($this->getReferencedCols())
|
|
){
|
|
$valid = true;
|
|
}
|
|
|
|
return $valid;
|
|
}
|
|
|
|
|
|
} |