Closed quasiperfect closed 10 years ago
Usually an error occurs "Database error XXX ..." an I see the generated SQL sentence. In most of the cases the cause is obvious.
Another option for debugging for tricky errors is temporarily to convert the result as SQL - see the method as_sql().
Also (3), I can use the profiler, it is to be configured to show the SQL queries.
This class extends functionality of the CodeIgniter's query builder, so approaches for catching errors are the same.
I have not published some complex code for demonstration. Here I will post some real code of mine, I am showing my style an habits, might be helpful for you.
I have a nomenclature "Social Neworks" that is a list of the most popular social networks that includes their names and icons. This is the database schema:
CREATE TABLE IF NOT EXISTS `social_networks` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(511) NOT NULL,
`image_32` varchar(511) NOT NULL,
`image_64` varchar(511) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '0',
`display_order` int(11) unsigned NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `display_order` (`display_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
In platform/core/common/models/ I create the model:
Social_networks.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
class Social_networks extends Core_Model {
protected $check_for_existing_fields = true;
public $protected_attributes = array('id');
protected $_table = 'social_networks';
protected $return_type = 'array';
public function __construct() {
parent::__construct();
$this->before_create[] = 'created_at';
$this->before_create[] = 'updated_at';
$this->before_update[] = 'updated_at';
}
}
This model contains only configuration data as you can see. I can use it in the front site directly. If I decide to change the database table name, I make only one name correction in this model.
In the admin panel, except CRUD, I need functionality for uploading/deletion images in the file system. So in platform/applications/admin/models/ I create an ancestor model:
Social_networks_manager.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
class Social_networks_manager extends Social_networks {
protected $upload_path;
protected $upload_url;
protected $allowed_types;
protected $max_size;
protected $max_width;
protected $max_height;
protected $thumb_max_width_64;
protected $thumb_max_height_64;
protected $thumb_max_width_32;
protected $thumb_max_height_32;
protected $data = array();
protected $errors = array();
public function __construct() {
parent::__construct();
$this->load->helper('file');
$this->upload_path = DEFAULTFCPATH.'upload/social/';
$this->upload_url = DEFAULT_BASE_URL.'upload/social/';
$this->allowed_types = 'gif|jpg|jpeg|png';
$this->max_size = 100;
$this->max_width = 64 ;
$this->max_height = 64;
$this->thumb_max_width_64 = 64 ;
$this->thumb_max_height_64 = 64;
$this->thumb_max_width_32 = 32 ;
$this->thumb_max_height_32 = 32;
}
public function upload_image_64($id, $field = 'image_64') {
$this->data = array();
$this->errors = array();
$id = (int) $id;
if (empty($id)) {
return $this;
}
$this->load->library('upload');
$this->lang->load('upload');
$this->load->library('image_lib');
$file_selected = isset($_FILES[$field]) && isset($_FILES[$field]['name']) && $_FILES[$field]['name'] != '';
if ($file_selected == '') {
return $this;
}
// Ivan: The uploaded image may not be valid, but I have to delete the previous image at this point.
$this->_delete_image_64($id);
$file_name = clean_file_name($_FILES[$field]['name']);
$config['file_name'] = $file_name;
$config['upload_path'] = $this->upload_path;
$config['allowed_types'] = $this->allowed_types;
$config['max_size'] = $this->max_size;
$config['max_width'] = $this->max_width;
$config['max_height'] = $this->max_height;
$config['overwrite'] = false;
$this->upload->initialize()->initialize($config, false);
if (!$this->upload->do_upload($field)) {
$this->errors = $this->upload->error_msg;
return $this;
}
$this->data = $this->upload->data();
if (!$this->data['is_image']) {
$this->errors[] = $this->lang->line('ui_invalid_image_format');
return $this;
}
if ($this->data['image_width'] != $this->thumb_max_width_64 || $this->data['image_height'] != $this->thumb_max_height_64) {
// Create the thumbnail.
$config_thumb = array();
$config_thumb['source_image'] = $this->data['full_path'];
$config['new_image'] = $this->data['full_path'];
$config_thumb['create_thumb'] = false;
$config_thumb['maintain_ratio'] = true;
$config_thumb['quality'] = 100;
$config_thumb['width'] = $this->thumb_max_width_64;
$config_thumb['height'] = $this->thumb_max_height_64;
$this->image_lib->initialize($config_thumb);
$this->image_lib->resize();
$this->image_lib->clear();
}
// Update the corresponding database record.
$this->update($id, array('image_64' => $this->data['file_name']));
return $this;
}
public function has_image_64($id) {
return $this->current_image_64($id) != '';
}
public function current_image_64($id) {
$id = (int) $id;
if (empty($id)) {
return '';
}
return trim($this->select('image_64')->as_value()->get($id));
}
public function delete_image_64($id) {
$this->data = array();
$this->errors = array();
$id = (int) $id;
if (empty($id)) {
return $this;
}
$this->data['file_name'] = $this->current_image_64($id);
return $this->_delete_image_64($id);
}
protected function _delete_image_64($id) {
$id = (int) $id;
if (empty($id)) {
return $this;
}
$file_name = $this->current_image_64($id);
if ($file_name == '') {
return $this;
}
$this->update($id, array('image_64' => ''));
@unlink($this->upload_path.$file_name);
return $this;
}
public function upload_image_32($id, $field = 'image_32') {
$this->data = array();
$this->errors = array();
$id = (int) $id;
if (empty($id)) {
return $this;
}
$this->load->library('upload');
$this->lang->load('upload');
$this->load->library('image_lib');
$file_selected = isset($_FILES[$field]) && isset($_FILES[$field]['name']) && $_FILES[$field]['name'] != '';
if ($file_selected == '') {
return $this;
}
// Ivan: The uploaded image may not be valid, but I have to delete the previous image at this point.
$this->_delete_image_32($id);
$file_name = clean_file_name($_FILES[$field]['name']);
$config['file_name'] = $file_name;
$config['upload_path'] = $this->upload_path;
$config['allowed_types'] = $this->allowed_types;
$config['max_size'] = $this->max_size;
$config['max_width'] = $this->max_width;
$config['max_height'] = $this->max_height;
$config['overwrite'] = false;
$this->upload->initialize()->initialize($config, false);
if (!$this->upload->do_upload($field)) {
$this->errors = $this->upload->error_msg;
return $this;
}
$this->data = $this->upload->data();
if (!$this->data['is_image']) {
$this->errors[] = $this->lang->line('ui_invalid_image_format');
return $this;
}
if ($this->data['image_width'] != $this->thumb_max_width_32 || $this->data['image_height'] != $this->thumb_max_height_32) {
// Create the thumbnail.
$config_thumb = array();
$config_thumb['source_image'] = $this->data['full_path'];
$config['new_image'] = $this->data['full_path'];
$config_thumb['create_thumb'] = false;
$config_thumb['maintain_ratio'] = true;
$config_thumb['quality'] = 100;
$config_thumb['width'] = $this->thumb_max_width_32;
$config_thumb['height'] = $this->thumb_max_height_32;
$this->image_lib->initialize($config_thumb);
$this->image_lib->resize();
$this->image_lib->clear();
}
// Update the corresponding database record.
$this->update($id, array('image_32' => $this->data['file_name']));
return $this;
}
public function has_image_32($id) {
return $this->current_image_32($id) != '';
}
public function current_image_32($id) {
$id = (int) $id;
if (empty($id)) {
return '';
}
return trim($this->select('image_32')->as_value()->get($id));
}
public function delete_image_32($id) {
$this->data = array();
$this->errors = array();
$id = (int) $id;
if (empty($id)) {
return $this;
}
$this->data['file_name'] = $this->current_image_32($id);
return $this->_delete_image_32($id);
}
protected function _delete_image_32($id) {
$id = (int) $id;
if (empty($id)) {
return $this;
}
$file_name = $this->current_image_32($id);
if ($file_name == '') {
return $this;
}
$this->update($id, array('image_32' => ''));
@unlink($this->upload_path.$file_name);
return $this;
}
public function upload_url() {
return $this->upload_url;
}
public function data() {
return $this->data;
}
public function errors() {
return $this->errors;
}
public function display_errors() {
if (empty($this->errors)) {
return '';
}
if (count($this->errors) > 1) {
return '<ul><li>'.implode('</li><li>', $this->errors).'</li></ul>';
}
return '<p>'.$this->errors[0].'</p>';
}
public function allowed_types() {
return $this->allowed_types;
}
public function max_size() {
return $this->max_size;
}
public function max_width() {
return $this->max_width;
}
public function max_height() {
return $this->max_height;
}
public function display_allowed_types() {
return str_replace('|', ', ', $this->allowed_types);
}
public function display_allowed_sizes() {
$result = array();
if (!empty($this->max_size)) {
$this->load->helper('number');
$label = $this->lang->line('ui_max_file_size');
$result[] = $label.': '.byte_format(1024 * $this->max_size, 0);
}
if (!empty($this->max_width)) {
$label = $this->lang->line('ui_max_width');
if ($this->lang->current() == 'bulgarian' && count($result) > 0) {
$label = UTF8::strtolower($label);
}
$result[] = $label.': '.$this->max_width.' px';
}
if (!empty($this->max_height)) {
$label = $this->lang->line('ui_max_height');
if ($this->lang->current() == 'bulgarian' && count($result) > 0) {
$label = UTF8::strtolower($label);
}
$result[] = $label.': '.$this->max_height.' px';
}
$result = implode('; ', $result);
return $result;
}
}
So, the additional functionality (as code) loads only in the admin panel where it is needed.
thanks a lot for taking the time to post this
i usually do this
public function get_items()
{
$result = FALSE;
$query = $this->db->query("my query here");
if ($query)
{
$result = array();
if ($query->num_rows() > 0)
{
$result = $query->result_array();
}
}
else
{
$error = $this->db->error();
log_message('error', "Error MySql. Code : " . $error['code'] . " Message : " . $error['message']);
}
return $result;
}
and then
$items = $this->model_m->get_items()
if (is_array($items))
{
if (!empty($items))
{
//do something with results
}
else
{
$page_data['message'] = 'Sorry there was a error';
}
}
else
{
$page_data['message'] = 'Sorry there was a error';
}
i wanted to replicate this behavior
hmm no luck in case of a error nothing is returned or i'm missing something what is returned when there is a error and debug is off ?
Concerning your get_items() method:
if (!empty($items))
{
//do something with results
}
else
{
$page_data['message'] = 'Sorry there was a error';
}
Here empty($items) means that there is no data within the table. So, the message should be "There is no data."
$items = $this->social_networks->find(); // All the items.
$items = $this->social_networks->select('id, name')->find(); // All the items with limited fields.
$items = $this->social_networks->select('id, name')->where('active', 1)->find(); // All the active items.
$items = $this->social_networks->where('active', 1)->dropdown('id', 'name'); // All the active items. The result here is convenient for filling options of a select box.
All these things are ready for use.
i understand all that my problem is how can i catch a fatal error by core_model because with CI it works like i described (i check the data before using it)
on a query with core model you try to do something with the data without checking if the data is actually there and that trows a fatal error when debug is on will show the error in production just a blank page and that's a bit of a problem
"my problem is how can i catch a fatal error by core_model" - This I don't understand. CodeIgniter catches fatal errors and logs them. There is no special debug layer in Core_Model.
Of couse, I validate data on entering.
And when I show a page based on a specific record with its id, I first check whether the id is valid (is it a number?) then a check whether the record exist. Then within the view I show either data or the message "The record does not exist."
Here is a sample controller of mine with all validation checks:
<?php defined('BASEPATH') OR exit('No direct script access allowed.');
class Social_network_controller extends Base_Controller {
public function __construct() {
parent::__construct();
$this->load
->model('social_networks_manager')
->language('social_networks')
;
$this->_set_nav('nomenclatures/social_networks');
}
public function index() {
$this->load->model('status_active');
$id = $this->_validate_id($this->uri->rsegment(3), $this->social_networks_manager, true, site_url('social-networks'));
$edit_mode = !empty($id);
$this->template->set_breadcrumb($this->lang->line('nomenclatures').': '.$this->lang->line('social_networks'), site_url('social-networks'));
if ($edit_mode) {
$item = $this->social_networks_manager->get($id);
if (empty($item)) {
$this->session->set_flashdata('error_message', $this->lang->line('ui_data_access_error').' (id = '.$id.')');
redirect(site_url('social-networks'));
}
$name = $item['name'];
$this->_set_title($this->lang->line('ui_edit').': '.$name);
} else {
$item = $this->social_networks_manager->get_empty();
$name = $item['name'];
$this->_set_title($this->lang->line('add_social_network'));
}
$item['status_name'] = $this->status_active->get_decorated_name($item['active']);
$validation_rules = array(
array(
'field' => 'name',
'label' => $this->lang->line('social_network'),
'rules' => 'nohtml|trim|required'
),
array(
'field' => 'active',
'label' => $this->lang->line('status'),
'rules' => 'nohtml|trim|required'
),
);
$this->form_validation->set_rules($validation_rules);
if ($this->form_validation->run()) {
// id and language validation.
if ($id != (int) $this->input->post('id')) {
$this->session->set_flashdata('error_message', $this->lang->line('ui_data_storage_error').' (id = '.$id.')');
redirect(site_url('social-networks'));
}
$data = $this->input->post();
// Store data.
if ($edit_mode) {
$this->social_networks_manager->update($id, $data);
} else {
$id = (int) $this->social_networks_manager->insert($data);
// Set the display order.
$this->social_networks_manager->update($id, array('display_order' =>
(int) $this->social_networks_manager
->select('MAX(display_order)')
->as_value()
->get_by()
+ 1
));
}
$name = $data['name'];
if (!empty($id)) {
// Process the uploaded images, if any.
$upload_errors = $this->social_networks_manager->upload_image_64($id, 'image_64')->errors();
$upload_data = $this->social_networks_manager->data();
if (!empty($upload_errors)) {
$this->session->set_flashdata('error_message', $this->social_networks_manager->display_errors());
redirect(CURRENT_URL);
}
$upload_errors = $this->social_networks_manager->upload_image_32($id, 'image_32')->errors();
$upload_data = $this->social_networks_manager->data();
if (!empty($upload_errors)) {
$this->session->set_flashdata('error_message', $this->social_networks_manager->display_errors());
redirect(CURRENT_URL);
}
}
$this->session->set_flashdata('confirmation_message', $this->lang->line('social_network_saved', '<strong>'.$name.'</strong>'));
redirect(site_url('social-networks'), 'refresh');
}
elseif (validation_errors()) {
$this->template->set('error_message', '<ul>'.validation_errors('<li>', '</li>').'</ul>');
$this->template->set('validation_errors', validation_errors_array());
}
$this->template
->set(compact('item', 'edit_mode'))
->set('image_64', $this->social_networks_manager->current_image_64($id))
->set('image_32', $this->social_networks_manager->current_image_32($id))
->set('upload_url', $this->social_networks_manager->upload_url())
->set('allowed_types', $this->social_networks_manager->display_allowed_types())
->set('allowed_sizes', $this->social_networks_manager->display_allowed_sizes())
->set_partial('scripts', 'social_network_scripts')
->enable_parser_body('i18n')
->build('social_network');
}
public function delete() {
$id = $this->_validate_id($this->uri->rsegment(3), $this->social_networks_manager, false, site_url('social-networks'));
$name = $this->social_networks_manager->select('name')->as_value()->get($id);
$this->social_networks_manager->delete_image_64($id);
$this->social_networks_manager->delete_image_32($id);
$this->social_networks_manager->delete($id);
$this->session->set_flashdata('confirmation_message', $this->lang->line('social_network_deleted', '<strong>'.$name.'</strong>'));
redirect(site_url('social-networks'));
}
public function delete_image_64() {
$id = $this->_validate_id($this->uri->rsegment(3), $this->social_networks_manager, false, site_url('social-networks'));
if ($this->social_networks_manager->has_image_64($id)) {
$name = $this->social_networks_manager->current_image_64($id);
$this->social_networks_manager->delete_image_64($id);
$this->session->set_flashdata('confirmation_message', $this->lang->line('image_deleted', '<strong>'.$name.'</strong>'));
}
redirect(site_url('social-network/'.$id));
}
public function delete_image_32() {
$id = $this->_validate_id($this->uri->rsegment(3), $this->social_networks_manager, false, site_url('social-networks'));
if ($this->social_networks_manager->has_image_32($id)) {
$name = $this->social_networks_manager->current_image_32($id);
$this->social_networks_manager->delete_image_32($id);
$this->session->set_flashdata('confirmation_message', $this->lang->line('image_deleted', '<strong>'.$name.'</strong>'));
}
redirect(site_url('social-network/'.$id));
}
public function _status_active_dropdown($value = '') {
$this->load->model('status_active');
return form_dropdown(
'active',
array('' => '-- '.$this->lang->line('ui_choose').' --') + $this->status_active->dropdown(),
$value,
'id="active" class="form-control" style="width: 100%;"'
);
}
}
With this approach I never see blank pages on the production server. I barely understand what you are trying to do, I don't see its value.
Within the Base_Controller I have the following method for id validation:
protected function _validate_id($id, $model, $allow_empty = true, $redirect_on_error_url = null) {
$id = trim($id);
if ($id != '' && !ctype_digit($id)) {
if (is_cli()) {
echo $this->lang->line('ui_invalid_data_access_identificator').' (id = '.$id.')';
exit(EXIT_ERROR);
}
if ($this->input->is_ajax_request()) {
exit;
}
$this->session->set_flashdata('error_message', $this->lang->line('ui_invalid_data_access_identificator').' (id = '.$id.')');
redirect($redirect_on_error_url);
}
if ($id == '' && !$allow_empty) {
if (is_cli()) {
echo $this->lang->line('ui_missing_data_access_identificator');
exit(EXIT_ERROR);
}
if ($this->input->is_ajax_request()) {
exit;
}
$this->session->set_flashdata('error_message', $this->lang->line('ui_missing_data_access_identificator'));
redirect($redirect_on_error_url);
}
$id = (int) $id;
if (empty($id) && $allow_empty) {
return $id;
}
if (!$model->exists($id)) {
if (is_cli()) {
echo $this->lang->line('ui_data_record_does_not_exist').' (id = '.$id.')';
exit(EXIT_ERROR);
}
if ($this->input->is_ajax_request()) {
exit;
}
$this->session->set_flashdata('error_message', $this->lang->line('ui_data_record_does_not_exist').' (id = '.$id.')');
redirect($redirect_on_error_url);
}
return $id;
}
My checks are within the controller, I don't need to pollute my models with them.
hmm i don't know how to make myself clear
lets say you do a order by a non-existent column or some other query mistake that would result in a mysql error
$this->model_m->select('something')->order_by('non-existent', 'desc')->get_all();
in production this will result in a blank page is there a way to catch that and echo a message
It is a low-level solution. The only way I see is you to hack the original file CodeIgniter/system/database/DB_driver.php, see the method
public function display_error($error = '', $swap = '', $native = FALSE)
At the end there is the following code:
$error =& load_class('Exceptions', 'core');
echo $error->show_error($heading, $message, 'error_db');
exit(8); // EXIT_DATABASE
On your production server, BEFORE this fragment of code, temporarily add the following:
var_dump($message);
exit(8); // Stop execution on first database error as the original CodeIgniter does.
Make a test in your production server, if you are able to see the error message, prettify this code, even make it dependent on configuration option. The white screen is a protection anyway.
Or, if the trick works, don't implement it permanently, just use it when it is needed, then revert back the original code.
Can you try this?
Edit: It is about CodeIgniter 3.
thanks for your time i will come back with some code after i'm done with my current project
Actually, I don't like such a feature.
hi
how do you catch a error using this ?
if ?