everyone
I'm a newbie with cakephp framework. and I want to calling data in database
using cakephp soap.
I using a component in this link >> http://bakery.cakephp.org/articles/char101/2009/06/05/a-component-to-help-creating-soap-services
This is my soap.php (SOAP Component)
<?php
App::import('Vendor', 'IPReflectionClass', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'IPReflectionClass.class.php'));
App::import('Vendor', 'IPReflectionCommentParser', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'IPReflectionCommentParser.class.php'));
App::import('Vendor', 'IPXMLSchema', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'IPXMLSchema.class.php'));
App::import('Vendor', 'IPReflectionMethod', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'IPReflectionMethod.class.php'));
App::import('Vendor', 'WSDLStruct', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'WSDLStruct.class.php'));
App::import('Vendor', 'WSDLException', array('file' => 'wshelper' . DS . 'lib' . DS . 'soap' . DS . 'WSDLException.class.php'));
/**
* Class SoapComponent
*
* Generate WSDL and handle SOAP calls
*/
class SoapComponent extends Component
{
var $params = array();
function initialize(&$controller)
{
$this->params = $controller->params;
}
/**
* Get WSDL for specified model.
*
* #param string $modelClass : model name in camel case
* #param string $serviceMethod : method of the controller that will handle SOAP calls
*/
function getWSDL($modelId, $serviceMethod = 'call')
{
$modelClass = $this->__getModelClass($modelId);
$expireTime = '+1 year';
$cachePath = $modelClass . '.wsdl';
// Check cache if exist
$wsdl = cache($cachePath, null, $expireTime);
// If DEBUG > 0, compare cache modified time to model file modified time
if ((Configure::read() > 0) && (! is_null($wsdl))) {
$cacheFile = CACHE . $cachePath;
if (is_file($cacheFile)) {
$modelMtime = filemtime($this->__getModelFile($modelId));
$cacheMtime = filemtime(CACHE . $cachePath);
if ($modelMtime > $cacheMtime) {
$wsdl = null;
}
}
}
// Generate WSDL if not cached
if (is_null($wsdl)) {
$refl = new IPReflectionClass($modelClass);
$controllerName = $this->params['controller'];
$serviceURL = Router::url("/$controllerName/$serviceMethod", true);
$wsdlStruct = new WSDLStruct('http://schema.example.com',
$serviceURL . '/' . $modelId,
SOAP_RPC,
SOAP_LITERAL);
$wsdlStruct->setService($refl);
try {
$wsdl = $wsdlStruct->generateDocument();
// cache($cachePath, $wsdl, $expireTime);
} catch (WSDLException $exception) {
if (Configure::read() > 0) {
$exception->Display();
exit();
} else {
return null;
}
}
}
return $wsdl;
}
/**
* Handle SOAP service call
*
* #param string $modelId : underscore notation of the called model
* without _service ending
* #param string $wsdlMethod : method of the controller that will generate the WSDL
*/
function handle($modelId, $wsdlMethod = 'wsdl')
{
$modelClass = $this->__getModelClass($modelId);
$wsdlCacheFile = CACHE . $modelClass . '.wsdl';
// Try to create cache file if not exists
if (! is_file($wsdlCacheFile)) {
$this->getWSDL($modelId);
}
if (is_file($wsdlCacheFile)) {
$server = new SoapServer($wsdlCacheFile);
} else {
$controllerName = $this->params['controller'];
$wsdlURL = Router::url("/$controllerName/$wsdlMethod", true);
$server = new SoapServer($wsdlURL . '/' . $modelId);
}
$server->setClass($modelClass);
$server->handle();
}
/**
* Get model class for specified model id
*
* #access private
* #return string : the model id
*/
function __getModelClass($modelId)
{
$inflector = new Inflector;
return ($inflector->camelize($modelId) . 'Service');
}
/**
* Get model id for specified model class
*
* #access private
* #return string : the model id
*/
function __getModelId($modelClass)
{
$inflector = new Inflector;
return $inflector->underscore(substr($class, 0, -7));
}
/**
* Get model file for specified model id
*
* #access private
* #return string : the filename
*/
function __getModelFile($modelId)
{
$modelDir = dirname(dirname(dirname(__FILE__))) . DS . 'models';
return $modelDir . DS . $modelId . '_service.php';
}
}
?>
This is my service_controller.php
<?php
class ServiceController extends AppController
{
public $name = 'Service';
public $uses = array('TestService');
public $helpers = array();
public $components = array('Soap');
/**
* Handle SOAP calls
*/
function call($model)
{
$this->autoRender = FALSE;
$this->Soap->handle($model, 'wsdl');
}
/**
* Provide WSDL for a model
*/
function wsdl($model)
{
$this->autoRender = FALSE;
header('Content-Type: text/xml; charset=UTF-8'); // Add encoding if this doesn't work e.g. header('Content-Type: text/xml; charset=UTF-8');
echo $this->Soap->getWSDL($model, 'call');
}
}
?>
this's my test_service.php
<?php
class TestService extends AppModel
{
var $name = 'TestService';
var $useTable = 'Test';
/**
* Get one record
* #param string
* #return string
*/
function view() {
$this->set('text', $this->TestService->find('all'));
return $text ;
}
}
?>
I want to Call data in database but my code can't to call from database.
and this's My Error in soapUi
Call to a member function find() on a non-object
Undefined property: TestService::$TestService
Change
$this->set('text', $this->TestService->find('all'));
To
$this->set('text', $this->find('all'));
Although it doesn't make sense to call ->set() within a model, I think you'd want to actually return instead:
return $this->find('all');
Related
I'm working on CakePHP 3.2.
I want to import data in bulk from excel file and save them to database. For this I'm using PHPExcel Library.
I have downloaded the library and extracted in vendor directory and thus the filepath to PHPExcel.php is
/vendor/PHPExcel/Classes/PHPExcel.php
and filepath to IOFactory.php is
/vendor/PHPExcel/Classes/PHPExcel/IOFactory.php
I'm including this in my controller like
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
include '../vendor/PHPExcel/Classes/PHPExcel.php';
include '../vendor/PHPExcel/Classes/PHPExcel/IOFactory.php';
/**
* Products Controller
*
* #property \App\Model\Table\ProductsTable $Products
*/
class ProductsController extends AppController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
if ($this->Auth->user()['status'] != 1) {
$this->Auth->deny(['sell']);
}
$this->Auth->allow(['bulkUpload']);
}
public function bulkUpload()
{
$inputFileName = $this->request->data('excel_data');
if ($inputFileName != '') {
$inputFileType = PHPExcel_IOFactory::identify($inputFileName); // line 33
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($inputFileName);
$objWorksheet = $objPHPExcel->setActiveSheetIndex(0);
$highestRow = $objWorksheet->getHighestRow();
for ($row = 2; $row <= $highestRow; ++$row) {
$this->data['Program']['cycle_month'] = $objWorksheet->getCellByColumnAndRow(1, $row)->getValue();
$this->data['Program']['cycle_year'] = $objWorksheet->getCellByColumnAndRow(2, $row)->getValue();
$this->data['Program']['media_partnum'] = $objWorksheet->getCellByColumnAndRow(3, $row)->getValue();
$resultArray[$row-2] = $this->data['Program'];
}
debug($resultArray);
}
}
}
Note : I have never used such plugin and bulk upload that is why I followed code from This Question on StackOverflow
Now, the problem is, When I select a file and upload, it gives error as
Class 'App\Controller\PHPExcel_IOFactory' not found at line 33
I think the problem is with calling PHPExcel_IOFactory class.
PHPExcel_IOFactory class is inside IOFactory.php file
<?php
/** PHPExcel root directory */
if (!defined('PHPEXCEL_ROOT')) {
/**
* #ignore
*/
define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../');
require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
}
class PHPExcel_IOFactory
{
/**
* Search locations
*
* #var array
* #access private
* #static
*/
private static $searchLocations = array(
array( 'type' => 'IWriter', 'path' => 'PHPExcel/Writer/{0}.php', 'class' => 'PHPExcel_Writer_{0}' ),
array( 'type' => 'IReader', 'path' => 'PHPExcel/Reader/{0}.php', 'class' => 'PHPExcel_Reader_{0}' )
);
/**
* Autoresolve classes
*
* #var array
* #access private
* #static
*/
private static $autoResolveClasses = array(
'Excel2007',
'Excel5',
'Excel2003XML',
'OOCalc',
'SYLK',
'Gnumeric',
'HTML',
'CSV',
);
/**
* Private constructor for PHPExcel_IOFactory
*/
private function __construct()
{
}
public static function identify($pFilename)
{
$reader = self::createReaderForFile($pFilename);
$className = get_class($reader);
$classType = explode('_', $className);
unset($reader);
return array_pop($classType);
}
}
I see it's a simple problem of namespaces. Just put a slash before the name class as it is in the global namespace
$inputFileType = \PHPExcel_IOFactory::identify($inputFileName);
You can use composer to load PHPExcel library. It will auto include all classes in your project.
composer require phpoffice/phpexcel
Cheers :)
I am being shown the following error on top of my page when using beforeSave method in my Upload model.
Strict (2048): Declaration of Upload::beforeSave() should be
compatible with Model::beforeSave($options = Array)
[APP/Model/Upload.php, line 5]
Could someone point out what I'm doing wrong?
Here is my model:
<?php
App::uses('AppModel', 'Model');
class Upload extends AppModel {
protected function _processFile() {
$file = $this->data['Upload']['file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$name = md5($file['name']);
$path = WWW_ROOT . 'files' . DS . $name;
if (is_uploaded_file($file['tmp_name'])
&& move_uploaded_file($file['tmp_name'], $path) ) {
$this->data['Upload']['name'] = $file['name'];
$this->data['Upload']['size'] = $file['size'];
$this->data['Upload']['mime'] = $file['type'];
$this->data['Upload']['path'] = '/files/' . $name;
unset($this->data['Upload']['file']);
return true;
}
}
return false;
}
public function beforeSave() {
if (!parent::beforeSave($options)) {
return false;
}
return $this->_processFile();
}
}
?>
Just change this line
public function beforeSave() {
to this, so you have correct method declaration
public function beforeSave($options = array()) {
The beforeSave() function executes immediately after model data has been successfully validated, but just before the data is saved. This function should also return true if you want the save operation to continue.
This callback is especially handy for any data-massaging logic that needs to happen before your data is stored. If your storage engine needs dates in a specific format, access it at $this->data and modify it.
Below is an example of how beforeSave can be used for date conversion. The code in the example is used for an application with a begindate formatted like YYYY-MM-DD in the database and is displayed like DD-MM-YYYY in the application. Of course this can be changed very easily. Use the code below in the appropriate model.
public function beforeSave($options = array()) {
if (!empty($this->data['Event']['begindate']) &&
!empty($this->data['Event']['enddate'])
) {
$this->data['Event']['begindate'] = $this->dateFormatBeforeSave(
$this->data['Event']['begindate']
);
$this->data['Event']['enddate'] = $this->dateFormatBeforeSave(
$this->data['Event']['enddate']
);
}
return true;
}
public function dateFormatBeforeSave($dateString) {
return date('Y-m-d', strtotime($dateString));
}
Make sure that beforeSave() returns true, or your save is going to fail.
I really need to know this issue to finish my work.
Here I'll give a example envolving model and controller.
I want to pass $final_name from controller to beforeSave() of my model
public function admin_add() {
if($this->request->is('post')) {
if($this->data['Client']['file']['tmp_name'] != '') {
// Upload block
$tmp_file = $this->data['Client']['file']['tmp_name'];
$file = new File($tmp_file);
if($file->mime() == "image/jpeg" or "image/png") {
$ext = explode('.', $this->data['Client']['file']['name']);
$name = md5($this->data['Client']['file']['name']);
$file->copy(IMG_DIR . 'portfolio\\' . $name . '.' . end($ext));
$final_name = $name . "." . end($ext); // File name with extension
}
// If save
if($this->Client->save($this->request->data)) {
$this->Session->setFlash('Client cadastrado com sucesso!', 'admin_flash');
}
}
}
}
In my client model
public function beforeSave($options = array()) {
if($this->data['Client']['file']['name'] != null) {
$this->data['Client']['file'] = $final_name;
}
return parent::beforeSave($options);
}
In Controller
$this->request->data['Client']['final_name'] = $name . "." . end($ext);
In Model
public function beforeSave($options = array()) {
if($this->data['Client']['file']['name'] != null) {
$this->data['Client']['file'] = $this->data['Client']['final_name'];
}
return parent::beforeSave($options);
}
Update for CakePHP3
Pass variable from controller to table beforeSave, afterSave ?
// Examples
// In Controller
$this->Article->save($data, ['passVariable' => 'passedData']);
// in Table
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
if (isset($options['passVariable'])) {
// implement your code
}
}
Read more: https://api.cakephp.org/3.8/class-Cake.ORM.Table.html#_save
But good place to modify data before save like asked in question are:
https://book.cakephp.org/3.0/en/orm/saving-data.html#before-marshal or
https://book.cakephp.org/3.0/en/orm/entities.html#accessors-mutators
I'm using cakephp framework.I want to export a database table of a client download in two formats.One is '.csv', the other is a '.txt'.CSV which I have passed 'csvHelper' resolved.Now I want to know how to export '.txt'.Sorry for my poor English, but I think you'll see what I mean, thanks!
Basically you set the header for the filename, like this:
$this->response->type('Content-Type: text/csv');
$this->response->download('myfilename.txt');
Here are some functions I put in my appController, so that any controller can output a CSV file if required:
/**
* Flattens the data that is returned from a find() operation, and puts it into CSV format
* #param $data
* #return string
*/
public function _arrayToCsvFile($data){
$flattenedData = array();
$exportKeyPair = array();
foreach($data as $datumKey => $datumDetails){
$flattenedDatum = Hash::flatten($datumDetails, '.');
$flattenedData[] = $flattenedDatum;
// Find all keys
if($datumKey == 0){
$exportKeys = array_keys($flattenedDatum);
$exportKeyPair = array_combine($exportKeys, $exportKeys);
}
else {
$datumKeys = array_keys($flattenedDatum);
$datumKeyPair = array_combine($datumKeys, $datumKeys);
// Add the datum keys to the exportKeyPair if it's not already there
$exportKeyPair = array_merge($exportKeyPair, $datumKeyPair);
}
}
$exportKeyMap = array();
foreach($exportKeyPair as $exportKey => $exportValue){
$exportKeyMap[$exportKey] = "";
}
$outputCSV = '';
$outputCSV .= $this->_arrayToCsvLine($exportKeyPair);
foreach($flattenedData as $flattenedDatumKey => $flattenedDatumDetails){
// Add any extra keys
$normalisedDatumDetails = array_merge($exportKeyMap, $flattenedDatumDetails);
$outputCSV .= $this->_arrayToCsvLine($normalisedDatumDetails);
}
return $outputCSV;
}
/**
* arrayToCsvLine function - turns an array into a line of CSV data.
*
* #access public
* #param mixed $inputLineArray the input array
* #param string $separator (default: ")
* #param string $quote (default: '"')
* #return string
*/
function _arrayToCsvLine($inputLineArray, $separator = ",", $quote = '"') {
$outputLine = "";
$numOutput = 0;
foreach($inputLineArray as $inputLineKey => $inputLineValue) {
if($numOutput > 0) {
$outputLine .= $separator;
}
$outputLine .= $quote . str_replace(array('"', "\n", "\r"),array('""', "", ""), $inputLineValue) . $quote;
$numOutput++;
}
$outputLine .= "\n";
return $outputLine;
}
/**
* Serves some CSV contents out to the client
* #param $csvContents
* #param array $options
*/
public function _serveCsv($csvContents, $options = array()){
$defaults = array(
'modelClass' => $this->modelClass,
'fileName' => null
);
$settings = array_merge($defaults, $options);
if(empty($settings['fileName'])){
$settings['fileName'] = strtolower( Inflector::pluralize( $settings['modelClass'] ) ) . '_' . date('Y-m-d_H-i') . '.csv';
}
$this->autoRender = false;
$this->response->type('Content-Type: text/csv');
$this->response->download($settings['fileName']);
$this->response->body($csvContents);
}
Now in any controller you can do this (note that you can pass the filename to _serveCsv if required):
/**
* admin_export_csv method
*
* #return void
*/
public function admin_export_csv() {
$this->Order->recursive = 0;
$conditions = array();
$orders = $this->Order->find('all', array('conditions' => $conditions));
$csvContents = $this->_arrayToCsvFile($orders); // Function in AppController
$this->_serveCsv($csvContents, array('filename' => 'export.txt')); // Function in AppController
}
you can use
$this->RequestHandler->respondAs('txt'); to output the correct header.
Please not the header is not set when DEBUG is greater than 2.
And you will still have to build the controller action and set view as ususal.
I've been researching a bit and I found that CakePHP's form helper doesn't interpret ENUM fields correctly, so it simply outputs a text input. I found a post that suggested to use a helper for that specific purpose. Does anybody know a better way to achieve this? Or if CakePHP devs intend to correct this some day?
Thanks for reading!
Below is one of the helper extention.
App::uses('FormHelper', 'View/Helper');
/**
* APP/View/Helper/MySqlEnumFormHelper.php
* It extends FormHelper to implement ENUM datatype of MySQL.
*
* http://blog.xao.jp/blog/cakephp/implementation-of-mysql-enum-datatype-in-formhelper/
*
* created Oct. 15, 2012
* CakePHP 2.2.3
*/
class MySqlEnumFormHelper extends FormHelper
{
public function input($fieldName, $options = array())
{
if (!isset($options['type']) && !isset($options['options'])) {
$modelKey = $this->model();
if (preg_match(
'/^enum\((.+)\)$/ui',
$this->fieldset[$modelKey]['fields'][$fieldName]['type'],
$m
)) {
$match = trim($m[1]);
$qOpen = substr($match, 0, 1);
$qClose = substr($match, -1);
$delimiter = $qOpen . ',' . $qClose;
preg_match('/^'.$qOpen.'(.+)'.$qClose.'$/u', $match, $m);
$_options = explode($delimiter, $m[1]);
$options['type'] = 'select';
$options['options'] = array_combine($_options, $_options);
}
}
return parent::input($fieldName, $options);
}
}
Cake attempts to be database agnostic and therefore this issue won't be "corrected" since it's not a bug. For example, SQL server doesn't have an exact equivalent of MySQL's ENUM field type.
I would recommend getting your possible list of enum values like so:
YourController.php
// get column type
$type = $this->Model->getColumnType('field');
// extract values in single quotes separated by comma
preg_match_all("/'(.*?)'/", $type, $enums);
// enums
var_dump($enums[1]);
Then use a select field in your view and pass the enums as options. Your current value you'll already have. How does that sound?
I am new to cakephp I found some old code and pieced together an enum select box for you enjoy
/**
* Behavior with useful functionality around models containing an enum type field
*
* Copyright (c) Debuggable, http://debuggable.com
*
*
*
* #package default
* #access public
*
* reworked by Nathanael Mallow for cakephp 2.0
*
*/
/*
*Use case:Add this (EnumerableBehavior.php) to app/Model/Behavior/
* -->in the Model add public $actsAs = array('Enumerable');
* -->in the *_controller add $enumOptions = $this->Categorie->enumOptions('Section');
* -->in the view add print $this->Form->input('{db_field_name}', array('options' => $enumOptions, 'label' => 'here'));
*
*
*/
class EnumerableBehavior extends ModelBehavior {
/**
* Fetches the enum type options for a specific field
*
* #param string $field
* #return void
* #access public
*/
function enumOptions($model, $field) {
//Cache::clear();
$cacheKey = $model->alias . '_' . $field . '_enum_options';
$options = Cache::read($cacheKey);
if (!$options) {
$sql = "SHOW COLUMNS FROM `{$model->useTable}` LIKE '{$field}'";
$enumData = $model->query($sql);
$options = false;
if (!empty($enumData)) {
$enumData = preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $enumData[0]['COLUMNS']['Type']);
$options = explode("','", $enumData);
}
Cache::write($cacheKey, $options);
}
return $options;
}
}
?>
If you want to use MySqlEnumFormHelper instead of normal and call it by $this->Form-> instead by $this->MySqlEnumFormHelper . You should add this line in your controller to alias MySqlEnumFormHelper as Form.
public $helpers = array('Form' => array(
'className' => 'MySqlEnumForm'
));
/* comments about previus answers ***
Use case:Add this (EnumerableBehavior.php) to app/Model/Behavior/
-->in the Model add public $actsAs = array('Enumerable');
-->in the action of the *_controller add $enumOptions = $this->YourModelName->enumOptions('db_field_name'); $this->set('enumOptions',$enumOptions);
-->in the view add print $this->Form->input('{db_field_name}', array('options' => $enumOptions, 'label' => 'here'));
*
*/
i think the Behaviour way it's good...but the array keys are integer
so i have modified the function like this
function enumOptions($model, $field) {
//Cache::clear();
$cacheKey = $model->alias . '_' . $field . '_enum_options';
$options = Cache::read($cacheKey);
$enumOptions = array();
if (!$options) {
$sql = "SHOW COLUMNS FROM `{$model->useTable}` LIKE '{$field}'";
$enumData = $model->query($sql);
$options = false;
if (!empty($enumData)) {
$enumData = preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $enumData[0]['COLUMNS']['Type']);
$options = explode("','", $enumData);
foreach ($options as $option) {
$enumOptions["$option"] = $option;
}
}
Cache::write($cacheKey, $enumOptions);
}
return $enumOptions;
}
in order to be able to save the right value in the db field when the form is submitted
I created a function that goes into AppController to handle this. I combined some of the information provided above.
Usage:
$enumList = getEnumValues($ModelField) where ModelField is in this format: 'Model.Field'
Function that I put in AppController:
function getEnumValues($ModelField){
// split input into Model and Fieldname
$m = explode('.', $ModelField);
if ($m[0] == $ModelField) {
return false;
} else {
(! ClassRegistry::isKeySet($m[0])) ? $this->loadModel($m[0]): false;
$type = $this->$m[0]->getColumnType($m[1]);
preg_match_all("/'(.*?)'/", $type, $enums);
foreach ($enums[1] as $value){$enumList[$value] = $value;}
return $enumList;
}
}