I'm working on a shopping cart (Cart model). One of its protected properties is "_items", which holds an array of Product objects. They (Products) all get stored in DB for populating the session (using ZF, Zend_Session_SaveHandler_DbTable() etc.).
public function addItem(Model_Product $product, $qty)
{
$qty = (int) $qty;
$pId = $product->getId();
if ($qty > 0) {
$this->_items[$pId] = array('product' => $product, 'qty' => $qty);
} else {
// if the quantity is zero (or less), remove item from stack
unset($this->_items[$pId]);
}
// add new info to session
$this->persist();
}
In the controller, I grab a Product obj from DB with the ProductMapper and provide it to "addItem()":
$product1 = $prodMapper->getProductByName('cap');
$this->_cart->addItem($product1, 2);
getProductByName() returns a new populated Model_Product object.
I usually get the
Please ensure that the class definition "Model_Product" of the object you are trying to operate on was loaded _before_ ...
error message, a session dump obviously shows
['__PHP_Incomplete_Class_Name'] => 'Model_Product'
I know about the "declaring the class before serializing it". My problem is this: how can I declare the Product class in addItem(), if it's injected (first param) in the first place? Wouldn't a new declaration (like new Model_Product()) overwrite the param (original object) in addItem()? Must I declare it in the Cart model again?
Besides, I'll surely get a Cannot redeclare class Model_Product if I... redeclare it in Cart.
In ZF's bootstrap, the session was started before autoloading.
/**
* Make XXX_* classes available
*/
protected function _initAutoloaders()
{
$loader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'XXX',
'basePath' => APPLICATION_PATH
));
}
public function _initSession()
{
$config = $this->_config->custom->session;
/**
* For other settings, see the link below:
* http://framework.zend.com/manual/en/zend.session.global_session_management.html
*/
$sessionOptions = array(
'name' => $config->name,
'gc_maxlifetime' => $config->ttl,
'use_only_cookies' => $config->onlyCookies,
// 'strict' => true,
// 'path' => '/',
);
// store session info in DB
$sessDbConfig = array(
'name' => 'xxx_session',
'primary' => 'id',
'modifiedColumn' => 'modified',
'dataColumn' => 'data',
'lifetimeColumn' => 'lifetime'
);
Zend_Session::setOptions($sessionOptions);
Zend_Session::setSaveHandler(new Zend_Session_SaveHandler_DbTable($sessDbConfig));
Zend_Session::start();
}
When I was getting the errors I was talking about, the method declaration was the other way around: _initSession() was first, then _initAutoloaders() - and this was the exact order ZF was processing them.
I'll test some more, but this seems to work (and logical). Thanks for all your suggestions.
Related
I'm pretty new to Phpspec testing and I don't know what is the correct way to test multiple scenarios when transforming a object to different response structure.
I need to check if price is correctly calculated. Here I have the Transformer spec test:
/**
* #dataProvider pricesProvider
*/
public function it_should_check_whether_the_prices_are_correct(
$priceWithoutVat,
$priceWithVat,
$vat,
Request $request,
Repository $repository
) {
$productIds = array(100001);
$result = array(
new Product(
'100001',
'MONSTER',
new Price(
$priceWithoutVat,
20,
'GBP',
null,
null
)
)
);
$expected = array(
array(
"productId" => "100001",
"brand" => "MONSTER",
"price" => array(
"amount" => $priceWithVat,
"vatAmount" => $vat,
"currencyCode" => "GBP",
"discountAmount" => (int)0
)
)
);
$repository->getResult(array(
Repository::FILTER_IDS => $productIds
))->willReturn($result);
$request->get('productIds')->willReturn(productIds);
/** #var SubjectSpec $transformedData */
$transformedData = $this->transform($request);
$transformedData->shouldEqual($expected);
}
public function pricesProvider()
{
return array(
array('123.456789', 14814, 2469),
array('60.00', 7200, 1200),
);
}
In my Transformer class I have a function which formats data to the correct format:
public function transform(Request $request)
{
$productIds = $request->get('productIds');
$productsResult = $this->repository->getResult(array(
Repository::FILTER_IDS => $productIds
));
$products = array();
foreach ($productsResult as $product) {
$products[] = $this->formatData($product);
}
return $products;
}
/**
* #param Product $product
* #return array
*/
private function formatData(Product $product)
{
return array(
'productId' => $product->getId(),
'brand' => $product->getBrandName(),
'price' => array(
'amount' => (int)bcmul($product->getPrice()->getAmountWithTax(), '100'),
'vatAmount' => (int)bcmul($product->getPrice()->getTaxAmount(), '100'),
'currencyCode' => $product->getPrice()->getCurrencyCode(),
'discountAmount' => (int)bcmul($product->getPrice()->getDiscountAmount(), '100')
)
);
}
The problem is, that I'm getting this error message:
316 - it should check whether the prices are correct
warning: bcmul() expects parameter 1 to be string, object given in
/src/AppBundle/Database/Entity/Product/Price/Price.php line 49
If I hard-code those values then the test is green. However I want to test varios prices and results, so I decided to use the dataProvider method.
But when dataProvider passes the $amountWithoutTax value, it's not string but PhpSpec\Wrapper\Collaborator class and because of this the bcmul fails.
If I change the $amountWithoutTax value to $priceWithoutVat->getWrappedObject() then Double\stdClass\P97 class is passed and because of this the bcmul fails.
How do I make this work? Is it some banality or did I completely misunderstood the concept of this?
I use https://github.com/coduo/phpspec-data-provider-extension and in composer.json have the following:
"require-dev": {
"phpspec/phpspec": "2.5.8",
"coduo/phpspec-data-provider-extension": "^1.0"
}
If getAmountWithTax() in your formatData method returns an instance of PhpSpec\Wrapper\Collaborator, it means that it returns a Prophecy mock builder instead of the actual mock, i.e. the one that you get by calling reveal() method. I don't know how your data provider looks like, but it seems that you're mocking your Price value objects instead of creating real instances thereof, and $product->getPrice() in your production code returns the wrong kind of object.
The solution would be either to create a real instance of the Price value object that's later returned by $product->getPrice() with new in the data provider, or by calling reveal() on that instance, like this (assuming $price is a mock object that comes from a type hinted parameter):
$product->getPrice()->willReturn($price->reveal());
So like the title says, I am having a hard time making an array property of one of my classes have it's values be declared as properties & methods.
I can successfully do this if the array is not a property of a class, but as soon as the array is dropped into a class, the script doesn't like those values, and throws me this error.
Fatal error: Constant expression contains invalid operations in C:\xampp\htdocs_webdev\repos\mcf\static\inc\classes\class.catalogue.php on line 17
I have both classes being included in a different .php called inc.classes.php. That file is then included in each page. Here is some code to better illustrate my issue,
Master Class File: inc.classes.php
// config
require_once('config/config.php'); // config file
// other tools
require_once(ROOT_DIR . 'inc/parsedown/Parsedown.php'); // tool that I am using for parsing .md files
// my classes
require_once(ROOT_DIR . 'inc/classes/class.vendor.php');
require_once(ROOT_DIR . 'inc/classes/class.catalogue.php');
Class A: class.vendor.php
class Vendor
{
public $vendor = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}
Class B: class.catalogue.php
class Catalogue
{
public $catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
// Class methods...
}
(mdReplace() is a small function located in a seperate php file called inc.functions.php. It's purpose is to replace a few keywords inside of the .md files that contain the sections' content.)
Apologies in advance if I am just blind as a bat right now and am missing something obvious.
You can't run methods on a class property like that. You'd need to set that up inside your construct:
class Catalogue
{
public $catalogue = array();
public function __construct()
{
$this->catalogue = array(
'1' => array(
$section = $markdown->text(file_get_contents(ROOT_DIR . catalogue/markdown/section1.md')),
$link = 'catalogue/pdf/section1.pdf,
$pdf = (ROOT_DIR . $link),
'title' => 'Section One',
'content' => mdReplace($section, $pdf, $link),
'theme' => 'purple',
'vendors' => array(
1 => $vendor->get('foo'),
2 => $vendor->get('bar'),
),
),
// '2' ...
);
}
// Class methods...
}
If you read php oop manual carefully, here what you will see:
Class member variables are called "properties"... They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
See the words
must not depend on run-time information
And your current definition of public $catalogue is dependant of some data that will be evaluated later. That's why you have fatal error.
So, as said the solution is to fill $catalogue data by calling some function - either explicitly or in a __construct for example.
As Farkie said, you can't run method calls on a class property like the way you did.
The reason is, those objects which you are trying to use are not initialised, and in order for them to work they must be initialised first.
Ex variables which can't directly be used in properly as they are not available to be used
$markdown
$pdf
$section
So for Class B you need to have your code written inside the constructor.
However whatever you have done for Class A is perfectly acceptable and it should work. I could see that you have a typo in the variable name. It should be $vendors as you are trying to refer it inside the function get() as $this->vendors[$data];
The following will work
class Vendor
{
public $vendors = array(
'foo' => array(
'name' => 'Foo Inc.',
'image' => (VENDOR_IMG . 'foo/foo-logo.png'),
),
'bar' => array(
'name' => 'Bar Co.',
'image' => (VENDOR_IMG . 'bar/bar-logo.png'),
),
);
public function get($data) {
if (array_key_exists($data, $this->vendors)) {
return $this->vendors[$data];
} else {
// throw error
}
}
// Class methods...
}
I am using Doctrine 2 in my Zend Framework 2 Project. I have now created a Form and create one of my Dropdowns with Values from the Database. My Problem now is that I want to change which values are used and not the one which I get back from my repository. Okay, here some Code for a better understanding:
$this->add(
array(
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'county',
'options' => array(
'object_manager' => $this->getObjectManager(),
'label' => 'County',
'target_class' => 'Advert\Entity\Geolocation',
'property' => 'county',
'is_method' => true,
'empty_option' => '--- select county ---',
'value_options'=> function($targetEntity) {
$values = array($targetEntity->getCounty() => $targetEntity->getCounty());
return $values;
},
'find_method' => array(
'name' => 'getCounties',
),
),
'allow_empty' => true,
'required' => false,
'attributes' => array(
'id' => 'county',
'multiple' => false,
)
)
);
I want to set the value for my Select to be the County Name and not the ID. I thought that I would need the 'value_options' which needs an array. I tried it like above, but get the
Error Message: Argument 1 passed to Zend\Form\Element\Select::setValueOptions() must be of the type array, object given
Is this possible at all?
I was going to suggest modifying your code, although after checking the ObjectSelect code i'm surprised that (as far as I can tell) this isn't actually possible without extending the class. This is because the value is always generated from the id.
I create all form elements using factories (without the ObjectSelect), especially complex ones that require varied lists.
Alternative solution
First create a new method in the Repository that returns the correct array. This will allow you to reuse that same method should you need it anywhere else (not just for forms!).
class FooRepository extends Repository
{
public function getCounties()
{
// normal method unchanged, returns a collection
// of counties
}
public function getCountiesAsArrayKeyedByCountyName()
{
$counties = array();
foreach($this->getCounties() as $county) {
$counties[$county->getName()] = $county->getName();
}
return $counties;
}
}
Next create a custom select factory that will set the value options for you.
namespace MyModule\Form\Element;
use Zend\Form\Element\Select;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CountiesByNameSelectFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $formElementManager)
{
$element = new Select;
$element->setValueOptions($this->loadValueOptions($formElementManager));
// set other select options etc
$element->setName('foo')
->setOptions(array('foo' => 'bar'));
return $element;
}
protected function loadValueOptions(ServiceLocatorInterface $formElementManager)
{
$serviceManager = $formElementManager->getServiceLocator();
$repository = $serviceManager->get('DoctrineObjectManager')->getRepository('Foo/Entity/Bar');
return $repository->getCountiesAsArrayKeyedByCountyName();
}
}
Register the new element with the service manager by adding a new entry in Module.php or module.config.php.
// Module.php
public function getFormElementConfig()
{
return array(
'factories' => array(
'MyModule\Form\Element\CountiesByNameSelect'
=> 'MyModule\Form\Element\CountiesByNameSelectFactory',
),
);
}
Lastly change the form and remove your current select element and add the new one (use the name that you registered with the service manager as the type key)
$this->add(array(
'name' => 'counties',
'type' => 'MyModule\Form\Element\CountiesByNameSelect',
));
It might seem like a lot more code (because it is) however you will benefit from it being a much clearer separation of concerns and you can now reuse the element on multiple forms and only need to configure it in one place.
I have a Product model for a multi site application.
Depending on the domain(site) I want to load different data.
For example instead of having a name and description fields in my database I have posh_name, cheap_name, posh_description, and cheap_description.
if I set something up like this:
class Product extends AppModel
{
var $virtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
}
Then it always works, whether accessed directly from the model or via association.
But I need the virtual fields to be different depending on the domain. So first I creating my 2 sets:
var $poshVirtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
var $cheapVirtualFields = array(
'name' => 'cheap_name',
'description' => 'cheap_description'
);
So these are my 2 sets, but how do I assign the correct one based on domain? I do have a global function called isCheap() that lets me know if I am on the lower end domain or not.
so I tried this:
var $virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
This gives me an error. Apparently you cannot assign variables in a Class definition like this.
So I put this in my Product model instead:
function beforeFind($queryData)
{
$this->virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
return $queryData;
}
This works ONLY when the data is accessed directly from the model, DOES NOT work when the data is accessed via model association.
There has got to be a way to get this to work right. How?
Well if I put it in the constructor instead of the beforeFind callback it seems to work:
class Product extends AppModel
{
var $poshVirtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
var $cheapVirtualFields = array(
'name' => 'cheap_name',
'description' => 'cheap_description'
);
function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
}
}
However, I am not sure if this is a CakePHP no no that can come back to bite me?
seems like the issue could be that the model association is a model that is built on the fly. eg AppModel
try and do pr(get_class($this->Relation)); in the code and see what the output is, it should be your models name and not AppModel.
also try and use:
var $poshVirtualFields = array(
'name' => 'Model.posh_name',
'description' => 'Model.posh_description'
);
var $cheapVirtualFields = array(
'name' => 'Model.cheap_name',
'description' => 'Model.cheap_description'
);
Using the latest CakePHP build 1.3.6.
I'm writing a custom datasource for a external REST API. I've got all the read functionality working beautifully. I'm struggling with the Model::save & Model::create.
According to the documentation, the below methods must be implemented (see below and notice it does not mention calculate). These are all implemented. However, I was getting an "Fatal error: Call to undefined method ApiSource::calculate()". So I implemented the ApiSource::calculate() method.
describe($model) listSources() At
least one of:
create($model, $fields = array(), $values = array())
read($model, $queryData = array())
update($model, $fields = array(), $values = array())
delete($model, $id
= null)
public function calculate(&$model, $func, $params = array())
{
pr($model->data); // POST data
pr($func); // count
pr($params); // empty
return '__'.$func; // returning __count;
}
If make a call from my model
$this->save($this->data)
It is calling calculate, but none of the other implemented methods. I would expect it to either call ApiSource::create() or ApiSource::update()
Any thoughts or suggustions?
Leo, you tipped me in the right direction. The answer was in the model that was using the custom datasource. That model MUST define your _schema.
class User extends AppModel
{
public $name = 'User';
public $useDbConfig = 'cvs';
public $useTable = false;
public $_schema = array(
'firstName' => array(
'type' => 'string',
'length' => 30
),
'lastName' => array(
'type' => 'string',
'length' => 30
),
'email' => array(
'type' => 'string',
'length' => 50
),
'password' => array(
'type' => 'string',
'length' => 20
)
);
...
}
I'm guessing that if you implement a describe() method in the custom datasource that will solve the problem too. In this case it needed to be predefined to authorize the saves and/or creation.
From the API: http://api13.cakephp.org/class/dbo-source#method-DboSourcecalculate
"Returns an SQL calculation, i.e. COUNT() or MAX()"
A quick search in ~/cake finds 20 matches in 8 files. One of those is the definition in dbo_source.php
The other seven are:
dbo_source.test.php
code_coverage_manager.test.php
code_coverage_manager.php
dbo_db2.php
model.php
tree.php
containable.php
Without delving too deeply into this, I suspect your problem lies in Model::save
You'll probably have to define a calculate method to suit the structure of your custom datasource because Cake won't know how to do that.