2010-08-19 17 views
9

Bueno, esta es una pregunta de diseño simple que me he preguntado muchas veces y nunca he encontrado una solución satisfactoria. Mi ejemplo es con php-sql, pero esto ciertamente se aplica también a otros idiomas.Diseño de la base de datos: ¿Coincidencia de claves de base de datos sql con las constantes php?

Tengo una pequeña tabla de base de datos que contiene muy pocas entradas y que casi nunca necesita actualizarse. por ejemplo, este usertype tabla:

usertype_id (primary key) | name  | description 
---------------------------+------------+------------------- 
1       | 'admin' | 'Administrator' 
2       | 'reguser' | 'Registered user' 
3       | 'guest' | 'Guest' 

Ahora en el código php, a menudo tienen que comprobar o comparar el tipo de usuario que estoy tratando. Dado que los tipos de usuario están almacenados en la base de datos, puedo:

1) Seleccione * de la tabla usertype en la creación de instancias de clases y guárdelo en una matriz.
Luego todos los ids están disponibles para el código, y puedo hacer una simple selección para obtener las filas que necesito. Esta solución requiere una matriz y una consulta db cada vez que se crea una instancia de la clase.

$query = "SELECT info, foo FROM user WHERE usertype_id = ".$usertypes['admin']; 

2) utilizar la columna de name para seleccionar la correcta usertype_id, por lo que pueden unirse eficazmente con otras tablas. Esto es más o menos equivalente a 1), pero sin necesidad de almacenar en caché toda la tabla tipo de usuario en el objeto php:

$query = "SELECT info, foo FROM user JOIN usertype USING (usertype_id) WHERE usertype.name = 'admin' "; 

3) Definir las constantes que coinciden con las llaves en la mesa de tipo de usuario:

// As defines 
define("USERTYPE_ADMIN",1); 
define("USERTYPE_REGUSER",2); 

//Or as class constants 
const USERTYPE_ADMIN = 1; 
const USERTYPE_REGUSER = 2; 

Y luego haz una simple selección.

$query = "SELECT info, foo FROM user WHERE usertype_id = " . USERTYPE_ADMIN; 

Ésta es probablemente la solución más eficiente de los recursos, pero es malo para mantener, ya que hay que actualizar la tabla y el código si es necesario modificar algo en la mesa .. usertype

4) Deseche la tabla usertype y solo guarde los tipos en el código php. Realmente no me gusta esto porque permite que cualquier valor entre en la base de datos y se asigne al tipo de usuario. Pero tal vez, considerando todo, no es tan malo y simplemente estoy complicando algo que debería ser simple ...

De todos modos, para resumir, la solución que más me gusta es la # 2 porque es coherente y con índice en usertype.name, no puede ser tan malo. Pero lo que a menudo he terminado usando es # 3, por eficiencia.

¿Cómo lo harías? ¿Alguna mejor solución?

(edit: consulta fija en el # 2)

Respuesta

0

Por qué no eliminar la normalización de la mesa DB así que en vez de tener usertype_id, que tendría usertype con el tipo de cadena (admin). Luego en PHP puedes hacer define('USERTYPE_ADMIN', 'admin');. Le ahorra tener que modificar dos lugares si desea agregar un tipo de usuario ...

Y si realmente está preocupado por cualquier valor al ingresar, siempre puede hacer que la columna sea un tipo de datos ENUM, por lo que se autogestiona ...

+0

Esto funcionará si la tabla tipo va a ser utilizado por una sola tabla (como en el ejemplo). Pero, ¿qué sucede si varias tablas necesitan usar el mismo tipo de enumeración? – Vincent

+0

Bueno, entonces necesitarás usar el método normalizado. Y luego te quedas atascado constantemente consultando el DB por los valores. O eso, o manteniéndolo en dos lugares (lo cual no es divertido) ... – ircmaxell

0

Para las tablas que contendrán valores "tipo" especialmente cuando se espera que dicha tabla cambie con el tiempo, tiendo a usar un enfoque simple: Añadir columna Varchar llamada hid (viene de "id legible por humanos") con clave única.Entonces llenarlo con id significativo para los seres humanos como:

usertype_id (primary key) | name  | description  | hid (unique key) 
---------------------------+------------+-------------------+--------------- 
1       | 'admin' | 'Administrator' | 'admin' 
2       | 'reguser' | 'Registered user' | 'user' 
3       | 'guest' | 'Guest'   | 'guest' 

Cuando se necesita el ID real que tendrá que hacer seleccionar sobre la base de la columna HID, es decir

select usertype_id from tablename where hid = "admin" 

Esto no es un método eficaz, pero garantizará la compatibilidad de su aplicación entre diferentes implementaciones (es decir, un cliente puede tener 1.admin, 2. invitado, otro cliente 1.admin, 2. usuario, etc.). Para su caso, creo que # 3 es bastante adecuado, pero si espera tener más de 10 roles de usuario diferentes, pruebe el enfoque "oculto".

+0

Esto es lo que quise decir con la solución n. ° 2. Aunque tengo que admitir que el nombre que he elegido para mi columna de ID legible para humanos ('name') no es muy intuitivo. – Vincent

+0

Oh, mi mal. Me confundí al unirme a la columna varchar (no creo que sea eficiente). por cierto, ¿esta sentencia SQL arrojará algún resultado? Una cosa más acerca de este enfoque: es una buena idea limitar los valores que puede contener con algo como: ereg ("^ [az] {1} [a-z0-9 _] {0,254} $", $ hidden) es decir, minúsculas, alfanumérico, guión bajo permitido. Es útil si no va a ser la única persona que puede ingresar valores ... – Ognyan

+0

Sí, la columna de nombre debe ser un índice único; de lo contrario, sería bastante ineficiente. Tiene razón, la consulta en el n. ° 2 es incorrecta: S: ¡solo la solucioné, gracias! – Vincent

0

¿Está utilizando algún tipo de marco aquí? ¿Se podrían almacenar estos valores en una sola fuente, un archivo de configuración, que crea una lista de los objetos en PHP y también rellena la tabla cuando carga la base de datos? Estoy pensando desde la perspectiva de Rails, ya que ha pasado un tiempo desde que escribí PHP. La solución allí probablemente sería accesorios.

0

¿Por qué no hacer que sólo

foreach (getdbarr("SELECT * FROM usertype") as $row) { 
    define($row['name'],$row['id']); 
} 
0

que no es necesario una combinación en cada consulta a buscar la información sobre los tipos/papeles. Puede mantener su modelo de "usuario" y los modelos de "rol" separados en los objetos de acceso a datos (DAO), especialmente dado que hay muy pocos registros para los tipos de usuario.

En la mayoría de los casos donde tengo un número limitado de opciones que de otra manera estaría uniendo contra una tabla grande, las guardo en caché en memcached como una matriz asociativa. En el caso de que necesite información sobre una relación en particular (como un rol) simplemente la cargo.

$user = DAO_User::get(1); // this pulls a JOIN-less record 
$role = $user->getRole(); // lazy-load 

El código para $ usuario-> getRole() puede ser algo como:

public function getRole() { 
    // This comes from a cache that may be called multiple 
    // times per request with no penalty (i.e. store in a registry) 
    $roles = DAO_UserRoles::getAll(); 

    if(isset($roles[$this->role_id])) 
    return $roles[$this->role_id]; 

    return null; // or: new Model_UserRole(); 
} 

Esto también funciona si desea mostrar una lista con 1000 usuarios en él. Simplemente puede representar valores para esa columna desde una única matriz asociativa de $ roles.

Esta es una importante mejora en el rendimiento en el extremo SQL, y ayuda en gran medida a reducir la complejidad en su base de código. Si tiene varias otras claves externas en la tabla de usuario, puede utilizar este enfoque para obtener la información necesaria cuando la necesite. También significa que puede tener clases Model_ * confiables sin tener que crear híbridos para cada combinación posible de tablas que pueda UNIR, lo cual es mucho mejor que simplemente obtener un conjunto de resultados, iterarlo y liberarlo.

Incluso con más de 100 filas a ambos lados de su JOIN, aún puede usar el enfoque de carga lenta para información poco frecuente o altamente redundante. Con un servicio de almacenamiento en caché razonable en su código, no hay penalización para llamar a DAO_UserRole :: get (1500) varias veces porque las llamadas posteriores durante la misma solicitud no deberían golpear la base de datos dos veces. En la mayoría de los casos, solo mostrará 10-25 filas por página de cada 1000, y la carga diferida le ahorrará a su motor de base de datos tener que UNIRSE a todas las filas externas antes de que realmente las necesite.

La razón principal para hacer un JOIN es si su lógica WHERE lo requiere, o si necesita ORDER BY datos desde una clave externa. Tratar a JOINs como prohibitivamente caro es una buena costumbre.

1

Casi siempre voy por la opción 3). Puede generar el código necesario automáticamente en función de lo que está disponible en la base de datos.Lo único que tienes que recordar es que tienes que ejecutar el script para actualizar/reescribir esa información cuando agregas otro rol (pero si usas phing o una herramienta de compilación similar para implementar tus aplicaciones, simplemente agrega una regla de compilación para su script de implementación y siempre se ejecutará cada vez que implemente su código: p).

0

Para tablas de búsqueda estáticas básicas, generalmente hago archivos estáticos constantes (como el # 3). Yo generalmente uso clases, tales como:

namespace Constants; 
class UserTypes { 
    const ADMIN = 1; 
    const USER = 2; 
    const GUEST = 3; 
} 

$id = Constants\UserTypes::ADMIN; 

Cuando estoy usando las operaciones de búsqueda que toma son un poco más variable, entonces voy a tirar en un objeto y luego almacenar en caché durante 24 horas. De esta forma, solo se actualiza una vez al día. Eso le ahorrará realizar viajes de ida y vuelta a la base de datos, pero le permitirá tratar las cosas en código fácilmente.

0

Sí, tienes razón al evitar el # 3 y quedarte con el # 2. En la medida de lo posible, las búsquedas como cuando utiliza una tabla de tipo de uso para contener los roles y luego relacionarlos con la tabla de usuarios utilizando los valores de id. Deben permanecer en la base de datos. Si usa constantes, entonces los datos siempre deben depender de su código php para ser interpretados. Además, puede hacer cumplir la integridad de los datos mediante el uso de claves externas (donde los servidores lo permitan) y le permitirá transferir los informes de su código php a otras herramientas de informes. El mantenimiento también se vuelve más fácil. Los administradores de bases de datos no necesitarán saber php para derivar los significados de los números si usaste el n. ° 3, si alguna vez se les pidiera ayuda para el desarrollo de informes. Puede que no parezca demasiado relevante, pero en términos de mantenimiento, el uso de procedimientos almacenados distintos de sql en su código php también sería fácil de mantener para el mantenimiento de varias maneras, y también será ventajoso para los DBA.

0

Iré por la opción n. ° 2 y utilizaré la unión tal como está destinada a ser utilizada. Nunca se sabe lo que arrojará el futuro, ¡siempre es mejor estar preparado hoy!

Con respecto a dejar la base de datos solo tanto como sea posible para tales operaciones, también existe la posibilidad de almacenamiento en caché en el largo plazo. Para esta ruta, dentro de PHP una opción es usar un caché de archivos, uno que solo se actualizará cuando el tiempo lo requiera. Para el marco que he creado, aquí hay un ejemplo; Me interesaría saber lo que la gente piensa:

Nota:

(LStore, LFetch, GetFileName) pertenecen a un objeto de caché, que es llamada estáticamente.

(Blobify y Unblobify) pertenecen a un objeto SystemComponent que siempre está vivo

Cada pieza de datos de caché tiene una llave. esta es la única cosa que siempre tiene que recordar

public function LStore($key,$data, $blnBlobify=true) { 
    /* Opening the file in read/write mode */ 
    $h = fopen(self::GetFileName($key, 'longstore'),'a+'); 
    if (!$h) throw new Exception('Could not write to cache'); 
    flock($h,LOCK_EX); // exclusive lock, will get released when the file is closed 
    fseek($h,0); // go to the start of the file 
    /* truncate the file */ 
    ftruncate($h,0); 
    if($blnBlobify==true) { $data = SystemComponent::Blobify(array($data)); } 
    If (fwrite($h,$data)===false) { 
     throw new Exception('Could not write to cache'); 
    } 
    fclose($h); 
} 
public function LFetch($key) { 
    $filename = self::GetFileName($key, 'longstore'); 
    if (!file_exists($filename)){ return false;} 
    $h = fopen($filename,'r'); 
    if (!$h){ return false;} 
    /* Getting a shared lock */ 
    flock($h,LOCK_SH); 
    $data = file_get_contents($filename); 
    fclose($h); 
    $data = SystemComponent::Unblobify($data); 
    if (!$data) { 
     /* If unserializing somehow didn't work out, we'll delete the file */ 
     unlink($filename); 
     return false; 
    } 
    return $data; 
} 
/* This function is necessary as the framework scales different directories */ 

private function GetFileName($key, $strCacheDirectory='') { 
    if(!empty($strCacheDirectory)){ 
     return SystemComponent::GetCacheAdd() . $strCacheDirectory.'/' . md5($key); 
    } else {  
     return SystemComponent::GetCacheAdd() . md5($key); 
    } 
} 
public function Blobify($Source){ 
    if(is_array($Source)) { $Source = serialize($Source); } 

    $strSerialized = base64_encode($Source); 

    return $strSerialized; 

} 
public function Unblobify($strSerialized){ 
    $Decoded = base64_decode($strSerialized); 
    if(self::CheckSerialized($Decoded)) { $Decoded = unserialize($Decoded); } 

    return $Decoded;  

} 
function CheckSerialized($Source){ 
    $Data = @unserialize($Source); 
    if ($Source === 'b:0;' || $Data !== false) { 
     return true; 
    } else { 
     return false; 
    } 
} 

Ahora, cuando se trata de acceder a los datos reales, sólo llamo una zona de alcance. Para asegurarme de que esté actualizado, le digo que almacene. En su caso, esto sería después de actualizar la tabla de tipo de uso.

+0

Sé que esto puede parecer excesivo para su necesidad, pero se puede utilizar una y otra vez en todo el sitio. – Simon

1

Yo sugeriría # 3 para evitar consultas inútiles, y evitar el riesgo de cambios de conducta si filas de la tabla base de datos existentes son modificados por cierto:

  • Adición de las constantes necesarias en la clase del modelo:

    class Role // + use namespaces if possible 
    { 
        // A good ORM could be able to generate it (see @wimvds answer) 
        const ADMIN = 1; 
        const USER = 2; 
        const GUEST = 3; 
    
        //... 
    

    }

  • Entonces consulta como esto tiene sentido:

    $query = "SELECT info, foo FROM user WHERE role_id = ".Role::ADMIN; 
    

    Con un ORM (p. Ej. Propel en el siguiente ejemplo) que va a terminar haciendo:

    $isAdminResults = UserQuery::create()->filterByRoleId(Role::ADMIN); 
    
Cuestiones relacionadas