Описание типов данных для wsdl во внешнем файле

Здесь я покажу пример описания типов во внешнем файле и подключении этого файла в wsdl-описании вашего веб-сервиса.
Когда у вас относительно немного методов выставленных для веб-сервиса и нет каких-либо сложных custom структур-типов данных, то описание этих самых типов можно реализовать прямо в основном wsdl файле. Когда же количество методов с нестардантными параметрами уже значительно, то для удобства описание типов данных можно вынести во внешний файл.
Обратите внимание, что этот импортируемый файл должен быть полноценным xml файлом — с блоком

<?xml version="1.0" encoding="UTF-8"?>

в начале документа.
Дальше структура примерно следующая:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:webservice.contentinn.com" xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:yournamespace">
  <xsd:complexType name="AuthHeader">
    <xsd:sequence>
      <xsd:element name="company" type="xsd:string"/>
      <xsd:element name="username" type="xsd:string"/>
      <xsd:element name="password" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>
  <xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="AuthHeaderElement" type="tns:AuthHeader"/>
  ...........

Так как wsdl файл по сути своей та же схема, то подключение одного файла в другой выполняется обычным для xsd-файлов методом:

<import namespace="YOURNAMESPACE" schemaLocation="RELATIVE_PATH_TO_YOUR_SCHEMA_FILE"/>

Файл описания типов данных необходимо подключать в блоке :

<wsdl:types xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/2001/XMLSchema">
      <import namespace="urn:yournamespace" schemaLocation="wsdlTypes.xsd"/>
    </xsd:schema>
</wsdl:types>

У меня лично на первых порах некоторые сложности вызвало подключение файла с описанием типов данных в основной wsdl файл. Особое внимание в этом случае необходимо уделить namespace’ам (аттрибут namespace тэга import). А именно, чтобы значение аттрибута namespace точно соответствовало аттрибуту targetNamespace в подключенном файле схемы. В нашем случае это значение urn:yournamespace.
Вот и всё. Теперь описание типов видно в основном файле описания веб-сервиса.

Удачи вам с «выходом в мир»!

Генерация колонок в таблице

Иногда бывает необходимо динамически построить таблицу по имеющемуся массиву данных.
Проблема заключается в правильной расстановке закрывающего и открывающего тега <tr> таблицы.
Вот мое решение, скажем, для случая трех колонок:

<table cellpadding="0" cellspacing="0" class="withLowCells" border="0">
  < ?php $step = 0; ?>
  < ?php $colsPerRow = 3 ?>
  < ?php $stepsCount = sizeof($statuses) ?>
  <tr>
  < ?php foreach ($statuses as $statusKey => $statusName): ?>
    < ?php if (0 == $step%$colsPerRow && (0 < $step)): // insert a new row each $colsPerRow iteration ?>
    </tr><tr>
    < ?php endif; ?>
    < ?php if (($step + 1) == $stepsCount): ?>
      < ?php $colspan = $colsPerRow - $step%$colsPerRow; ?>
    < ?php endif; ?>
    < ?php // add colspan for last TD element to result with a valid table ?>
    <td <?php if (isset($colspan) && (1 < $colspan)): ?>colspan="< ?php echo $colspan ?>"< ?php endif;?>>
      < ?php echo checkbox_tag('filters[service_status][' . $statusKey . ']', $statusKey, isset($filters['service_status'][$statusKey])) ?> -
			<label for="filters_service_status_<?php echo $statusKey ?>" class="textTooltip" style="font-size: 11px; color: #000;" title="< ?php echo $statusName ?>">< ?php echo $statusKey ?></label>
    </td>
    < ?php $step++; // increment the step ?>
  < ?php endforeach; ?>
  </tr>
</table>

Если вы хотите, чтобы последняя ячейка не растягивалась на пустое место через colspan, а достраивалось недостающее число ячеек, то в блоке

< ?php if (($step + 1) == $stepsCount): ?>
  < ?php $colspan = $colsPerRow - $step%$colsPerRow; ?>
< ?php endif; ?>

$colspan нужно еще уменьшить на единицу
и после закрывающего тега </td> добавить блок кода

  < ?php if (isset($colspan) && (1 < $colspan)): ?>
    < ?php for ($i=0; $i < $colspan; $i++): ?>
      <td>&nbsp;</td>
    < ?php endfor; ?>
  < ?php endif; ?>

Symfony, Yaml отключение файла

Бывает так, что в некоторых конфигурационных файлах для представления view.yml необходимо отключить некоторые из файлов — css или javascript. Как известно, массив в yml можно определить как

key:
  - value1.css
  - value2.css
  - value3.css

Отлючается определенный файл с помощью директивы «-» (обычный дефис).
Иногда при отключении файла можно случайно либо по незнанию некорректно поставить прочерк (между знаком отключения и название файла появится пробел), т.е., к примеру при желании отключить второй файл value2.css мы напишем:

key:
  - value1.css
  - - value2.css
  - value3.css

В таком случае наша директива не сработает.
Корректно писать так:

key:
  - value1.css
  - -value2.css
  - value3.css

Об этом явно говорится в документации. Но часто бывает так, что кто ж её читает, когда есть «гугль в помощь»!

Переносимость wsdl файлов между серверами

Думаю у многих, кто сталкивался с разработкой вебсервисов с описанием их через WSDL файлы, возникала проблема, когда при переносе с dev-сервера на prod-сервер приходилось менять путь к расположению веб-сервера, к примеру:

<soap:address xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" location="http://localhost/soapServer.php"/>

или, скажем, когда урл сервера используется в качестве namaspace, и хочется, чтобы на прод-сервере фигурирувал именно его урл, а не какой-нибудь localhost.
При переносе между серверами частенько забывается поменять упомянутые выше параметры wsdl файла и бывает так, что веб-сервис не хочет работать, пока об этом не вспомнишь.

Во избежание таких ситуаций можно предложить простой, и довольно очевидный метод — использование РНР для генерации адреса сервера!

Т.е. мы отдаем клиенту не WSDL в чистом виде, а результат, полученый после обработки его РНР интерпретатором. Тогда мы можем использовать РНР функции и встроенные переменные, в частности переменную $_SERVER, которая позволит нам получить необходимую информацию.

Т.о., мы сможем написать путь к местоположению сервера как

<soap:address xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" location="http://' . $_SERVER['SERVER_NAME'] . '/soapServer.php"/>

Вот и вся магия! Возможно, данная заметка кому-то будет полезной.

Symfony sfThumbnailPlugin как виджет формы

В процессе разработки одного веб-сайта в админ-панели мне понадобилось реализовать возможность загрузки фотографии к создаваемой записи. Задача, само собой разумеется частая и востребованная. Так как разработку сайта на делал на основе фреймворка symfony, то решил первым делом порыскать среди готовых плагинов.

Поставленную задачу с успехом должен был решить плагин sfThumbnailPlugin. В симфони (>=1.2), как известно, для загрузки файлов служит виджет sfWidgetFormInputFileEditable. Данный виджет представляет привычное поле выбора файла из файловой системы. За саму же загрузку файла на сервер отвечает валидатор для указанного виджета. Таким образом, задача для меня состояла в том, чтобы «прикрутить» плагин создания «превьюшек» к стандартному виджету.

После углубленного изучения жизненного пути файла в недрах виджетов и валидаторов symfony (от момента его выбора до загрузки на сервер) мне показалось самым логичным использовать функциональность sfThumbnailPlugin именно в валидаторе загружаемого файла. То-есть, после нажатия кнопки сохранения какой-либо записи загруженный файл передается на обработку собственноручно написаного валидатора. Свой файл валидатора я назвал sfValidatorFileThumb и поместил в папку lib/validators проекта.

Класс конфигурации формы выглядит следующим образом (для примера*):

//................................
public function configure()
{
  $this->widgetSchema['photo'] = new sfWidgetFormInputFileEditable(
    array(
      'file_src'  => '/uploads/doctors/thumb/' . $this->getObject()->getPhoto(),
      'is_image'  => true,
      'with_delete' => true,
      'delete_label'  => "Удалить фото"
    )
);

Валидатор под этот виджет:

$this->validatorSchema['photo'] = new sfValidatorFileThumb(
  array(
    'path'        => sfConfig::get('sf_upload_dir') . DIRECTORY_SEPARATOR . 'doctors',
    'required'    => false,
    'mime_types'  => 'web_images',
    // Part below concerning a thumbnail
    'with_thumb'  => true,
    'thumb_path'  => sfConfig::get('sf_upload_dir') . DIRECTORY_SEPARATOR . 'doctors/thumb',
    'thumb_scale' => true,
    'thumb_inflate' => true,
    'thumb_quality' => 80, // must be in a range [0-100]
    'thumb_dimensions'  => array('width' => 116, 'height' => 154)
)
);

Сам же файл валидатора имеет следующий код:

class sfValidatorFileThumb extends sfValidatorFile
/**
* Configures the current validator.
*
* @access protected
*
* @param array $options   An array of options
* @param array $messages  An array of error messages
*/
protected function configure($options = array(), $messages = array())
{
// First configure the parent object
parent::configure($options, $messages);
 
// Add options
$this->addOption('with_thumb', isset($options['with_thumb']) ? $options['with_thumb'] : false);
$this->addOption('thumb_path', isset($options['thumb_path']) ? $options['thumb_path'] : sfConfig::get('sf_upload_dir') . DIRECTORY_SEPARATOR . 'assets');
$this->addOption('thumb_dimensions', isset($options['thumb_dimensions']) ? $options['thumb_dimensions'] : null);
$this->addOption('thumb_scale', isset($options['thumb_scale']) ? $options['thumb_scale'] : false);
$this->addOption('thumb_inflate', isset($options['thumb_inflate']) ? $options['thumb_inflate'] : false);
$this->addOption('thumb_quality', isset($options['thumb_quality']) ? $options['thumb_quality'] : false);
 
// Add messages:
//    $this->addMessage('thumb_path', 'Path does not exists or directory is not writeble!');
 
// Add our custom class that will handle uploaded file
$this->addOption('validated_file_class', 'withThumbnailValidatedFile');
}
 
/**
* Make validation for a file
*
* @access protected
* @author Roman Dushko
*
* @see sfValidatorFile
*/
protected function doClean($value)
{
// Validated file object
$validatedFileObj = parent::doClean($value);
 
if ($this->getOption('with_thumb'))
{
// Check if plugin sfThumpnailPlugin exists
if (!class_exists('sfThumbnail')) // @todo: Could we not to check if plugin is enabled? Or should we check?
{
throw new Exception(sprintf('A class for making the thumbnails "%s" is not found', 'sfThumbnail'));
}
 
// Check if a path where thumbnail will be stored is provided
if(null === $this->getOption('thumb_path'))
{
throw new Exception('Path to thumbnails is not provided!');
}
 
// Set needed validated file properties
$validatedFileObj->setWithThumbnail(true);
$validatedFileObj->setThumbProperties(
array(
'thumb_path' => $this->getOption('thumb_path'),
'thumb_dimensions' => $this->getOption('thumb_dimensions'),
'thumb_scale' => $this->getOption('thumb_scale'),
'thumb_inflate' => $this->getOption('thumb_inflate'),
'thumb_quality' => $this->getOption('thumb_quality')
)
);
}
 
return $validatedFileObj;
}
 
}
 
class withThumbnailValidatedFile extends sfValidatedFile
{
protected $withTumbnail = false;
protected $thumbProperties = array(
'thumb_path' => null,
'thumb_dimensions' => null,
'thumb_scale' => false,
'thumb_inflate' => false,
'thumb_quality' => null
);
 
public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777)
{
// Save original image
$savedFile = parent::save($file, $fileMode, $create, $dirMode);
 
// Check if we need to upload a thumbnail
if ($this->withTumbnail)
{
// Get dimension for thumbnail
$thumbDimensions = $this->getThumbProperty('thumb_dimensions');
if ($thumbDimensions)
{
$thumbWidth = isset($thumbDimensions['width']) ? $thumbDimensions['width'] : null;
$thumbHeight = isset($thumbDimensions['height']) ? $thumbDimensions['height'] : null;
}
 
// Create instance of thumbnailer
/**
* @todo: make it possible to define 'adapterClass' for sfThumbnail (and it's options) in factories.yml
*/
$thumnailer = new sfThumbnail(
$thumbWidth,
$thumbHeight,
$this->getThumbProperty('thumb_scale'),
$this->getThumbProperty('thumb_inflate'),
$this->getThumbProperty('thumb_quality')
);
// Load saved file
$thumnailer->loadFile($this->getSavedName());
// Save a thumbnail
$thumnailer->save($this->getThumbProperty('thumb_path') . DIRECTORY_SEPARATOR . basename($this->getSavedName()));
}
 
return $savedFile;
}
 
public function setWithThumbnail($with_thumb = false)
{
$this->withTumbnail = $with_thumb;
}
 
public function getWithThumbnail()
{
return $this->withTumbnail;
}
 
public function setThumbProperties($properties)
{
$this->thumbProperties = $properties;
}
 
public function getThumbProperties()
{
return $this->thumbProperties;
}
 
public function getThumbProperty($propName)
{
return $this->thumbProperties[$propName];
}
 
}

Собственно, за создание «превьюшки» и загрузки файлов на сервер отвечает класс withThumbnailValidatedFile, расширяющий sfValidatedFile, а именно его метод save, переопределяющий родительский метод.

В самом классе валидатора файла-превьюшки мы указываем, кто будет отвечать за обработку провалидированного файла:

$this->addOption('validated_file_class', 'withThumbnailValidatedFile');

Вот и всё! Естественно возможны дальнейшие улучшения, но для меня данное решение оказалось подходящим. При выборе файла и сохранении записи мы делаем две операции:

  1. Сохраняем оригинальное изображение
  2. Создаем и сохраняем thumbnail с помощью «допиленного» валидатора.
sfThumbnailPlugin