Mostrando entradas con la etiqueta Doctrine. Mostrar todas las entradas
Mostrando entradas con la etiqueta Doctrine. Mostrar todas las entradas

sábado, 30 de julio de 2011

Using BETWEEN syntax with Symfony 1.4.x and MS SQL Server 2008

I´m using Symfony 1.4.8 and MS SQL Server 2008.

I had a query that read:



$q = Doctrine_Query::create()
->select('m.mapdate, m.created_at')
->from('GISMap m')
->where('m.mapdate BETWEEN ? AND ?', array($start_date, $end_date))
->orderBy('m.mapdate DESC');

This worked perfectly in MySQL, but when I switched to MS SQL Server I got an error like this one:



500 | Internal Server Error | Doctrine_Connection_Mssql_Exception



SQLSTATE[HY000]: General error: 10007 Incorrect syntax near '?'. [10007] (severity 5) [SELECT [g].[id] AS [g__id], [g].[mapdate] AS [g__mapdate], [g].[created_at] AS [g__created_at] FROM [gis_map] [g] WHERE ([g].[mapdate] BETWEEN '2010-03-24 10:32:24' AND ?) ORDER BY [g].[mapdate] DESC]. Failing Query: "SELECT [g].[id] AS [g__id], [g].[mapdate] AS [g__mapdate], [g].[created_at] AS [g__created_at] FROM [gis_map] [g] WHERE ([g].[mapdate] BETWEEN '2010-03-24 10:32:24' AND ?) ORDER BY [g].[mapdate] DESC"



After doing some research I found out that the replacement of the ? for the actual values is done by the PDO driver. So I decided that the problem was the driver for SQL Server didn´t like the array being passed to it.



I change the syntax to:

$q = Doctrine_Query::create()
->select('m.mapdate, m.created_at')
->from('GISMap m')
->where('m.mapdate >= ?', $start_date)
->andWhere('m.mapdate <= ?', $end_date)
->orderBy('m.mapdate DESC');

Now it works. It´s annoying to have to do this, but it´s been my experience that working with SQL Server will get you into these troubles.






martes, 8 de marzo de 2011

Using Pagination with Symfony and Doctrine 2

When I blogged Using Pagination with Symfony and Doctrine I had being trying to understand the inner working of the Symfony's Admin Generator. At the time it seem a good solution. But, when I started implementing the idea, I realized that I had to repeat very similar code on each of the of the actions of the modules I was working.
So I decided to try a different aproach. I put all de code I was using in the actions in a utility class, created a widget to render the header and added a partial to render how many items in the table and the total number of pages. The final goal was to create my first Symfony plugin, but I'm not quite there yet, so I decided to blog first this improvement.
The Pagination Control
Since it's the same control I proposed in my previous blog Using Pagination with Symfony and Doctrine I will not repeat the code. You still need to get the images for buttons and place them in your web\images folder.
The Pagination Info Control
Create a file named /myproject/apps/myapplication/templates/_pagination_info.php and copy the following code on it. Then save it.
<div class="pagination_info">
<?php echo format_number_choice('[0] no result|[1] 1 result|(1,+Inf] %1% results',
array('%1%' => $pager->getNbResults()), $pager->getNbResults(), 'sf_admin') ?>
<?php if ($pager->haveToPaginate()): ?>
<?php echo __('(page %%page%%/%%nb_pages%%)', array('%%page%%' => $pager->getPage(),
'%%nb_pages%%' => $pager->getLastPage()), 'sf_admin') ?>
<?php endif; ?>
</div>
Creating the Paging Utility
Create a file named /myproject/lib/model/PagingUtility.class.php and copy the following code on it. Then save it.
This class will take care of all the code previously had to copy into the actions.
class PagingUtility {
private $modelname;
private $sortkey;
private $user;
private $modulename;

public function __construct($modelname, $user) {
$this->modelname=$modelname;
$this->user =$user;
$this->sortkey =strtolower($this->modelname).".sort";
$this->modulename = strtolower($this->modelname) . "_module";
}

public function buildPager($request, $items_x_page,$query=NULL){
if ($request->getParameter('sort') && $this->isValidSortColumn(
$request->getParameter('sort'))) {
$this->setSort(array($request->getParameter('sort'),
$request->getParameter('sort_type')));
}

// $items_x_page = sfConfig::get('app_max_items_x_page');
$pager = new sfDoctrinePager($this->modelname, $items_x_page);
//$device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
$query = is_null($query) ? $this->buildQuery() : $query;
$pager->setQuery($query);
$pager->setPage($request->getParameter('page', 1));
$pager->init();
return $pager;
// $this->sort = $this->getSort();
}
protected function buildQuery() {
$et = Doctrine_Core::getTable($this->modelname);

$query = $et->createQuery();

$this->addSortQuery($query);

return $query;
}

protected function addSortQuery($query) {
if (array(null, null) == ($sort = $this->getSort())) {
return;
}

if (!in_array(strtolower($sort[1]), array('asc', 'desc'))) {
$sort[1] = 'asc';
}
$query->addOrderBy($sort[0] . ' ' . $sort[1]);
}

public function getSort() {
if (null !== $sort = $this->user->getAttribute($this->sortkey,
null, $this->modulename)) {
return $sort;
}

$this->setSort(array(null, null));

return $this->user->getAttribute($this->sortkey,
null, $this->modulename);
}

protected function setSort(array $sort) {
if (null !== $sort[0] && null === $sort[1]) {
$sort[1] = 'asc';
}
$this->user->setAttribute($this->sortkey, $sort, $this->modulename);
}

protected function isValidSortColumn($column) {
return Doctrine::getTable($this->modelname)->hasColumn($column);
}
}
Creating the Sortable Header Widget
I'm not sure if creating a widget for this purpose would be the best approach, but I wanted to make my own widget a see how it worked. The code is an adaption of the code from a previous blog entry Sorting Lists in Symfony.
Create a file named /myproject/lib/widget/maSortableHeader.php and copy the following code on it. Then save it.
class maSortableHeader extends sfWidgetForm {

protected function configure($options = array(), $attributes = array()) {
$this->addOption('fieldname');
$this->addOption('fieldlabel');
$this->addOption('routename');
$this->addOption('sort');
}

public function render($name, $value = null, $attributes = array(), $errors = array()) {
$sort = $this->getOption('sort');

if ($this->getOption('fieldname') == $sort[0]) {
$config = array('query_string' => 'sort=' . $this->getOption('fieldname') . '&sort_type=' .
($sort[1] == 'asc' ? 'desc' : 'asc'));
$uri = link_to($this->getOption('fieldlabel'), $this->getOption('routename'),
$config);
$uri .= image_tag('/images/' .
$sort[1] . '.png', array('alt' => $sort[1],
'title' => $sort[1]));
} else {
$uri = link_to($this->getOption('fieldlabel'), $this->getOption('routename'),
array('query_string' => 'sort=' . $this->getOption('fieldname') . '&sort_type=asc'));
}

return $uri;
//optional - for easy css styling
}

}
Editing the Action


On our action we're going to implement our PagingUtiliy and add widets for two columns (Code and Name). Our executeIndex function looks like this:
public function executeIndex(sfWebRequest $request)
{
$pu = new PagingUtility("Category", $this->getUser());
$items_x_page = sfConfig::get('app_max_items_x_page');
$this->pager = $pu->buildPager($request, $items_x_page);

$this->sort = $pu->getSort();

//Adding SortableHeaders
$this->form = new sfForm();
$this->form->setWidget('code_sorted',
new maSortableHeader(array('fieldname'=>'code',
'fieldlabel'=>'Code', 'routename'=>'@category','sort'=>$pu->getSort())));
$this->form->setWidget('name_sorted',
new maSortableHeader(array('fieldname'=>'Name',
'fieldlabel'=>'Name', 'routename'=>'@category','sort'=>$pu->getSort())));

}
You still need to get the asc.png and desc.png into your web/images folder for the widget to show icon besides the header.

Editing the indexSuccess.php
What we need to do now is show the Sortable Headers, change the default loop to use the pager, add the pagination control and the pagination info partials.
<thead>
<tr>
<th><?php echo $form['code_sorted']; ?></th>
<th><?php echo $form['name_sorted']; ?></th>
<th>Description</th>
<th>Created at</th>
<th>Updated at</th>
<th>Created by</th>
<th>Updated by</th>
</tr>
</thead>
<tbody>
<?php foreach ($pager->getResults() as $i => $category): ?>
<tr>
<td><a href="<?php echo url_for('category/edit?id='.$category->getId()) ?>"><?php echo $category->getCode() ?></a></td>
<td><?php echo $category->getName() ?></td>
<td><?php echo $category->getDescription() ?></td>
<td><?php echo $category->getCreatedAt() ?></td>
<td><?php echo $category->getUpdatedAt() ?></td>
<td><?php echo $category->getCreator() ?></td>
<td><?php echo $category->getUpdator() ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="7">
<?php include_partial('global/pagination_info', array('pager' => $pager)) ?>
<?php include_partial('global/pagination_control', array('pager' => $pager, 'module' => 'category')) ?>
</td>
</tr>

Editing the Routing.yml
This is the step I always forget. Mainly because I don't quite undertand what it does. I will look into it an post my findings. For now it will not work unless you add the follwing code to your routing.yml.
category:
class: sfDoctrineRouteCollection
options:
model: Category
module: category
prefix_path: /category
column: id
with_wildcard_routes: true

domingo, 7 de noviembre de 2010

Using Pagination with Symfony and Doctrine

In order to understand how the Doctrine pager works, I created a admin module with the admin generator task and started snooping around the cache to see what it generated.

This is how I figured it out, may no be pretty but it works.

Create a Pagination Control


Create a file named /myproject/apps/myapplication/templates/_pagination_control.php and copy the following code on it. Then save it.

<div class="pagination">
<a href="<?php echo url_for($module) ?>?page=1">
<?php echo image_tag('/images/first.png', array('alt' =>
__('First page', array(), 'sf_admin'), 'title' =>
__('First page', array(), 'sf_admin'))) ?>
</a>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getPreviousPage() ?>">
<?php echo image_tag('/images/previous.png', array('alt' =>
__('Previous page', array(), 'sf_admin'), 'title' =>
__('Previous page', array(), 'sf_admin'))) ?>
</a>

<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<?php echo $page ?>
<?php else: ?>
<a href="<?php echo url_for($module) ?>?page=
<?php echo $page ?>"><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getNextPage() ?>">
<?php echo image_tag('/images/next.png', array('alt' =>
__('Next page', array(), 'sf_admin'), 'title' =>
__('Next page', array(), 'sf_admin'))) ?>
</a>

<a href="<?php echo url_for($module) ?>?page=
<?php echo $pager->getLastPage() ?>">
<?php echo image_tag('/images/last.png', array('alt' =>
__('Last page', array(), 'sf_admin'), 'title' =>
__('Last page', array(), 'sf_admin'))) ?>
</a>
</div>

You'll need to have the last.png, first.png, next.png and previous.png on your /myproject/web/images directory in order to get the pretty arrows. You can get the png files from any source you like, I extracted them from the my symonfy installation /symfony_installation/data/web/sf/sf_admin/images.

Editing the Action


Open your /myproject/apps/myapplication/config/app.yml and add the variable max_items_x_page:
all:
max_items_x_page: 5

Open your action file in my case the module is named repairrequest and the class is RepairRequest.
On the executeIndex function change the code to:
public function executeIndex(sfWebRequest $request) {
if ($request->getParameter('sort') && $this->isValidSortColumn(
$request->getParameter('sort'))) {
$this->setSort(array($request->getParameter('sort'),
$request->getParameter('sort_type')));
}

$items_x_page = sfConfig::get('app_max_items_x_page');
$this->pager = new sfDoctrinePager('RepairRequest', $items_x_page);
$this->pager->setQuery($this->buildQuery());
$this->pager->setPage($request->getParameter('page', 1));
$this->pager->init();
$this->sort = $this->getSort();
}

Change RepairRequest for your class.

Insert the following code at the end of your Action class:
//******************************************************************************************
//********************** ROUTINES FOR PAGING AND SORTING ***********************************
//******************************************************************************************
protected function buildQuery() {
$et = Doctrine_Core::getTable('RepairRequest');

$query = $et->createQuery();

$this->addSortQuery($query);

return $query;
}

protected function addSortQuery($query) {
if (array(null, null) == ($sort = $this->getSort())) {
return;
}

if (!in_array(strtolower($sort[1]), array('asc', 'desc'))) {
$sort[1] = 'asc';
}
$query->addOrderBy($sort[0] . ' ' . $sort[1]);
}

protected function getSort() {
if (null !== $sort = $this->getUser()->getAttribute('repairrequest.sort',
null, 'admin_module')) {
return $sort;
}

$this->setSort(array(null, null));

return $this->getUser()->getAttribute('repairrequest.sort', null,
'admin_module');
}

protected function setSort(array $sort) {
if (null !== $sort[0] && null === $sort[1]) {
$sort[1] = 'asc';
}
$this->getUser()->setAttribute('repairrequest.sort', $sort, 'admin_module');
}

protected function isValidSortColumn($column) {
return Doctrine::getTable('RepairRequest')->hasColumn($column);
}


Replace RepairRequest for your class name and repairrequest.sort for yourmodulename.sort.

Editing the Template


Open your /myproject/apps/myapplication/modules/mymodule/templates/indexSuccess.php at the top of the page ad the helper
for i18n.
<?php use_helper('I18N') ?>


The following code will change depending on your class
<tbody>
<?php foreach ($pager->getResults() as $i => $repair_request): ?>
<tr>
<td><a href="<?php echo url_for('repairrequest/show?id='.$repair_request->getId()) ?>">
<?php echo $repair_request->getId() ?></a></td>
<td><?php echo $repair_request->getRequestDate() ?></td>
<td><?php echo $repair_request->getLocation() ?></td>
<td><?php echo $repair_request->getDescription() ?></td>
<td><?php echo $repair_request->getRepairType() ?></td>
<td><?php echo $repair_request->getRepairStatus() ?></td>
<td><?php echo $repair_request->getRealStateId() ?></td>
<td><?php echo $repair_request->getUpdatedAt() ?></td>
<td><?php echo $repair_request->getUpdatedByFk()->getUsername() ?></td>
</tr>
<?php endforeach; ?>
</tbody>

What your doing is using the pager instead of the full cursor your get by default.
Next insert the pagination code.
<?php include_partial('global/pagination_control', array('pager' => $pager, 'module' => 'repairrequest')) ?>
<a href="<?php echo url_for('repairrequest/new') ?>">New</a>

Save it.
Now your pagination should work. On a next post I'll show how to do the sorting.
After I ran the code i got a message as if the route repairrequest did not exist (and it did). I fixed it editing the routing.yml (but I'm not really sure why I had to, just hacked it). I added the following code at the top of the routing.yml:
repairrequest:
class: sfDoctrineRouteCollection
options:
model: RepairRequest
module: repairrequest
prefix_path: /repairrequest
column: id
with_wildcard_routes: true

martes, 23 de marzo de 2010

Guardando Imágenes en la Base de Datos con Symfony 1.4

Aunque existen varias escuelas de pensamiento sobre si las imágenes se deben o no guardar en la base de datos. Para mis necesidades especificas es un requerimiento.

Symfony es un framework muy poderoso que apenas estoy empezando a aprender a utilizar. Sin embargo dentro de la documentación no encontré un buen ejemplo de como guardad una imagen en la base de datos como un BLOB y como recuperarla para despliegue.

Después de algo de investigación encontré un excelente posting en un forum del Symfony titulado How to upload an Image to a DB under Symfony 1.4 & display image to an end user por René Kabis donde explicaban como cargar y desplegar las imágenes en un BLOB.

El problema que tuve no era con el posting si no con mi falta de experiencia con Symfony. El posting asume que el usuario tiene al menos experiencia básica con Symfony, asi que decidí crear un guía un poco más detallada.

Creando el Proyecto sfMapGallery





  1. Abra Netbeans y vaya a Menu>New Project...



  2. Seleccione PHP Application y haga clic en Next


  3. Tecle el como Project Name sfMapGallery y haga clic en Next


  4. Cambie el Project URL a http://localhost:9091/ y haga clic en Next este paso asume que su configuración en Apache es en esta dirección


  5. Seleccione Symfony PHP Framework y haga clic en Finish





Creando la Base de Datos


Por defecto se crea el archivo Sources Files\config\databases.yml el mismo debe verse como este:

all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: mysql:host=localhost;dbname=sfMapGallery
username: root
password:

A continuación crearemos una base de de datos con el nombre mapgallery.



  1. Edite el archivo Sources Files\config\databases.yml y cambie el dbname por mapgallery

    dsn: mysql:host=localhost;dbname=mapgallery
  2. Abra phpMyAdmin (con WAMP el url es http://localhost/phpmyadmin/) y cree una nueva base de datos con el nombre mapgallery



  3. Copie el siguiente contenido en el archivo Sources Files\config\doctrine\schema.yml

    Map:
    tableName: maps
    connection: doctrine
    actAs: [Timestampable]
    options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8
    columns:
    id:
    type: integer(4)
    primary: true
    autoincrement: true
    filename:
    type: string(50)
    image:
    type: blob
    tags:
    type: string(150)

  4. Corra el comando symfony doctrine:build --all --no-confirmation . Este comando debe crear la tabla en la base de datos y el modelos, las formas y los filtros en Sources Files\lib

Creando el Validador de Imágenes




  1. Cree el directorio Sources Files\lib\validator

  2. Copy el archivo <Directorio Instalación Symfony>\lib\validator\sfValidatorFile.class.php

  3. Renombre el archivo Sources Files\lib\validator\sfValidatorFile.class.php a sfValidatorImgToDB.class.php

  4. Abra el archivo sfValidatorImgToDB.class.php y :

    1. Cambie el nombre de la clase a sfValidatorImgToDB
      ...
      class sfValidatorImgToDB extends sfValidatorBase {
      ...
    2. En el método configure adicione la opcion resolution (aprox. linea 75) y comente las optiones validated_file_class y path.
      ...
      $this->addOption('mime_categories', array(
      'web_images' => array(
      'image/jpeg',
      'image/pjpeg',
      'image/png',
      'image/x-png',
      'image/gif',
      )));
      $this->addOption('resolution');
      //$this->addOption('validated_file_class', 'sfValidatedFile');
      //$this->addOption('path', null);

      $this->addMessage('max_size', 'File is too large (maximum is %max_size% bytes).');
      ...

    3. En el método doClean comente las dos últimas lineas e inserte el código mostrado
      ...
      if ($this->hasOption('mime_types')) {
      $mimeTypes = is_array($this->getOption('mime_types')) ? $this->getOption('mime_types') : $this->getMimeTypesFromCategory($this->getOption('mime_types'));
      if (!in_array($mimeType, array_map('strtolower', $mimeTypes))) {
      throw new sfValidatorError($this, 'mime_types', array('mime_types' => $mimeTypes, 'mime_type' => $mimeType));
      }
      }

      //$class = $this->getOption('validated_file_class');
      //return new $class($value['name'], $mimeType, $value['tmp_name'], $value['size'], $this->getOption('path'));

      // check resolution

      if ($this->hasOption('resolution')) {
      $resolution = (is_array($this->getOption('resolution')) && count($this->getOption('resolution'))==2) ? $this->getOption('resolution') : null;
      if (is_int($resolution[0]) && is_int($resolution[1]) && ($resolution[0]/$resolution[1] == 4/3)) {
      $width = $resolution[0];
      $height = $resolution [1];
      }else {
      $width = 1024;
      $width = 768;
      $ratio = 4/3;
      }
      }

      return base64_encode($this->catchImage($value['tmp_name'], $width, $height));
      }

      ...

    4. Después del metodo doClean agregue los metodos catchImage y resizeImage
      ...
      public function catchImage($image, $width, $height) {
      if($image) {
      $data=fread(fopen($image, "r"), filesize($image));
      unlink($image);
      return $this->resizeImage(imagecreatefromstring($data), $width, $height);
      }
      }

      public function resizeImage($image, $width, $height) {
      $old_width = imagesx($image);
      $old_height = imagesy($image);
      $ratio=$old_width/$old_height;
      $resized_img = imagecreatetruecolor($width,$height);
      $white = imagecolorallocate($resized_img, 255, 255, 255);
      imagefill($resized_img, 0, 0, $white);
      if($ratio>(4/3)) {
      $new_width=$width;
      $new_height=($width*($old_height/$old_width));
      $x_offset=0;
      $y_offset=(($height-$new_height)/2);
      }elseif($ratio<(4/3)) { $new_width=($height*($old_width/$old_height)); $new_height=$height; $x_offset=(($width-$new_width)/2); $y_offset=0; }else { $new_width=$width; $new_height=$height; $x_offset=0; $y_offset=0; } imagecopyresampled($resized_img, $image, $x_offset, $y_offset, 0, 0, $new_width, $new_height, $old_width, $old_height); ob_start(); imageJPEG($resized_img); $new_image = ob_get_contents(); ob_end_clean(); return $new_image; }

      ...

    5. Guarde el archivo



  5. Abra el archivo Sources Files\lib\form\doctrine\MapForm.class.php y agreue el siguiente código en el método configure.

    class MapForm extends BaseMapForm
    {
    public function configure()
    {
    $this->setWidgets(array(
    'id' => new sfWidgetFormInputHidden(),
    'filename' => new sfWidgetFormInputText(),
    'image' => new sfWidgetFormInputFileEditable(array('label'=>'Map', 'file_src'=>'', 'is_image'=>true, 'edit_mode'=>!$this->isNew(),'template'=>'%input%')),
    'tags' => new sfWidgetFormInputText(),
    'created_at' => new sfWidgetFormDateTime(),
    'updated_at' => new sfWidgetFormDateTime(),
    ));

    $this->setValidators(array(
    'id' => new sfValidatorDoctrineChoice(array('model' => $this->getModelName(), 'column' => 'id', 'required' => false)),
    'filename' => new sfValidatorString(array('max_length' => 50, 'required' => false)),
    'image' => new sfValidatorImgToDB(array('resolution'=>array(1024, 768), 'mime_types'=>array('image/jpeg', 'image/jpg', 'image/pjpeg'), 'required'=>true)),
    'tags' => new sfValidatorString(array('max_length' => 150, 'required' => false)),
    'created_at' => new sfValidatorDateTime(),
    'updated_at' => new sfValidatorDateTime(),
    ));

    $this->widgetSchema->setNameFormat('map[%s]');

    $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
    }
    }

Probando el Validador


  1. Corra el commando symfony doctrine:generate-module --with-show frontend maps Map

  2. Abra la dirección http://localhost:9091/frontend_dev.php/maps


Desplegando la Imagen





  1. Corra el comando symfony generate:module frontend image. Esto creará la carpeta image en Source Files\apps\frontend\modules.


  2. Abra el archivo Source Files\apps\frontend\modules\templates\indexSuccess.php agregue el siguiente código
    <?php echo $image; ?>


  3. Abra el archivo Source Files\apps\frontend\modules\image\actions\actions.class.php reemplaze el código del método executeIndex por el siquiente:
    ...

    public function executeIndex(sfWebRequest $request) {
    $idreq = $request->getParameter('id');
    $data = Doctrine::getTable('Map')->find($idreq)->getData();
    $this->image = base64_decode($data["image"]);
    sfConfig::set('sf_web_debug', false);
    sfConfig::set('sf_escaping_strategy', false);
    }
    ...
  4. Cree el directorio Source Files\apps\frontend\modules\image\config

  5. Cree el archivo Source Files\apps\frontend\modules\image\config\view.yml, agregue el siguiente contenido al archivo:
    indexSuccess:
    has_layout: false
    http_metas:
    content-type: image/jpeg
    downloadSuccess:
    has_layout: false
    http_metas:
    content-type: image/jpeg
  6. Abra el archivo Source Files\apps\frontend\config\routing.yml , y agregue el siguiente código antes del registro default:
    ...
    default_index:
    url: /:module
    param: { action: index }
    image_handler:
    url: /image/:id/*
    class: sfRoute
    options:
    model: Photo
    type: object
    param: { module: image, action: index }
    requirements:
    id: \d+
    sf_method: [GET]

    default:
    url: /:module/:action/*
  7. Abra el archivo Source Files\apps\frontend\modules\photo\templates\indexSuccess.php y cambie el codigo que despliega la imagen:
    ...
    <td><?php echo $map->getFilename() ?></td>
    <td><img src="<?php echo url_for('@default?module=image&action=index&id='.$map->getId()) ?>" height="100px"/> </td>
    <td><?php echo $map->getTags() ?></td>
    ...

viernes, 12 de marzo de 2010

Oracle, Doctrine y Symfony

Después de años de resistirme a PHP, por considerlo un lenguage de scripting, recientemente me vi forzado a involucrarme con el. Después de años de programar en Java, C# y Visual Basic el cambio fue un poco traumático. Sin embargo mi experiencia con Python me ayudo un poco.
En mi busqueda de frameworks que hicieran lo que ya conocia en Java como Hibernate, Struts, etc. Me tropece con Symfony.

Symfony es un Framework basado en el paradigma Model-View-Controller (MVC), es elegante y muy versatil. Sin embargo su curva de apredizaje es mayor que la otros frameworks como por ejempo CodeIgniter. Symfony soporta dos ORMs Doctrine y Propel, decidí trabaja con Doctrine porque encontre la documentación más clara.

La mayoría de los ejemplos que encontré eran usando como RDBMS MySQL, sin embargo no encontré mucho para Oracle y Doctrine. También tuve problemas con las conexiones a Oracle al principio asi que decidi escribir este artículo para tenerlo de referencia en futuras conexiones.

Las tareas de Doctrine no corren desde NetBeans


El primer problema que tuve fue que las tareas de symfony no encontraban el conector oci a pesar que habia editado el php.ini. El problema se debía a que WAMP utiliza dos php.ini uno para Apache (<directorio instalación WAMP>\bin\apache\Apache2.2.11\bin\php.ini) y otro que está en el raiz de la la instalación de php (<directorio instalación WAMP>/bin/php/php5.3.0/php.ini). Cuando trataba de correr las tareas de Doctrine desde Netbeans 6.8 mandaba un error. Una vez active el active el php_oci8.dll en php.ini que se encontraba en el raíz de php el problema se solucionó.

Definición del DSN de Oracle en Symfony

La versión de Oracle con la que estoy trabajando es 10gR2 asi que utilice el conector php_pdo_oci.dll. Aparentemente el php_oci8.dll no es requerido para conectarse con Doctrine.
La sintaxis que me funcionó en el databases.yml fue la siguiente:

all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: oci://[user]:[password]@[server_url]/(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=[server_url])(PORT=1521)))(CONNECT_DATA=(SID=[namespace])))


La información del TNS se encuentra en el archivo tnsame.ora (en mi caso se ecuentra en C:/Oracle/product/10.1.0/Client_1/network/ADMIN/tnsnames.ora).