Adding Cart functions for Amazon API - php

I am trying to add cart functions to the AmazonECS class available at https://github.com/Exeu/Amazon-ECS-PHP-Library
The main class of that project is https://github.com/Exeu/Amazon-ECS-PHP-Library/blob/master/lib/AmazonECS.class.php
It currently supports ItemLookup and ItemSearch but it does not have CartCreate, CartClear, CartAdd, CartGet, CartModify.
Amazon's documentation about these API calls can be found on this page
http://docs.aws.amazon.com/AWSECommerceService/2011-08-01/DG/CartCreate.html
Here's one of the things I have tried which hasn't worked.
/**
* execute CartCreate request
*
* #param string $asin, $associateTag
*
* #return array|object return type depends on setting
*
* #see returnType()
*/
public function cartCreate($asin, $associateTag)
{
$params = $this->buildRequestParams('CartCreate', array(
array('Item.1.ASIN' => $asin, 'Item.1.Quantity' => 1),
'AssociateTag' => $associateTag,
));
return $this->returnData($this->performSoapRequest("CartCreate", $params));
}
Does anyone know what I'm doing wrong? The error message I get back from that call is
string(79) "Your request is missing required parameters. Required parameters include Items."

For anyone who is still searching for an answer to this, I have this solution for you. The following is my cart function:
public function CartCreate($OfferListingId)
{
$params = $this->buildRequestParams('CartCreate', array(
'Items' => array(
'Item' => array(
//You can also use 'ASIN' here
'OfferListingId' => $OfferListingId,
'Quantity' => 1,
)
)
));
return $this->returnData(
$this->performSoapRequest("CartCreate", $params)
);
}
The parameters you send should be constructed as an associated array.
Also, in case you get an error about not being able to add the item with ASIN to the cart, remember to add the country code to the Request, since the default is US and, for example, in my case I needed items from UK. This is how I do my Request:
$cart = $this->ecs->country('UK')->ResponseGroup('Cart')->CartCreate($OfferListingId);

I may be wrong, but I don't think your Item.1.ID should be part of another array. Try this instead:
$params = $this->buildRequestParams('CartCreate', array(
'Item.1.ASIN' => $asin,
'Item.1.Quantity' => 1,
'AssociateTag' => $associateTag,
));

Related

How to properly test subclasses with phpspec dataprovider

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());

PHP ElasticSearch how to set mapping before indexing records?

I'm using laravel and elasticsearch-php to index and store data to elastic, my problem is that elastisearch uses from dynamic mapping but I need to set my custom mapping. How can I use from my mapping?
Bellow is my code:
$client = \Elasticsearch\ClientBuilder::create()->build();
$mappingData = array(
'index' => 'promote_kmp',
'body' => array(
'mappings' => $resource->getMappingProperties()
)
);
$client->indices()->create($mappingData);
$params = [
'type' => 'resources',
'id' => uniqid(),
'body' => [
'id' => $resource->id,
'name' => $resource->name,
'display_name_en' => $resource->display_name_en,
'display_name_pr' => $resource->display_name_pr,
'display_name_pa' => $resource->display_name_pa,
'table_name' => $resource->table_name,
'model_name' => $resource->model_name,
'in_sidemenu' => $resource->in_sidemenu,
'icon_class' => $resource->icon_class,
'created_at' => $resource->created_at,
'created_by' => $user,
]
];
//$response = $client->indices()->create($resource->getMappingProperties());
$client->index($params);
$resource->getMappingProperties() get the mapping array I have set in model.
but when I want to index a record it says IndexAlreadyExistsException[[promote_kmp] already exists]. This question arise when I want to search for date field searching is not properly working and I guess that mapping is not true.
As I was saying in comments.
The code is executing the creation of index every time you want to query.
But the index must be created only once.
So it should work like the migration for the DB's.
The only idea I can give you is to make a command to generate the index.
So that you could just
$ artisan elasticsearch:generate <index>
About the code, what I've done for our case, made the index with a way to inject the types, plus a way to create them into elasticsearch:
interface Index {
/**
* #param Type[] $types Index types (resources)
*/
function setTypes(array $types);
/**
* Generate the index and the types into the elasticsearch
*/
function create();
}
Then the types should generate the mappings and the type name (as /<index>/<type>, like:
interface Type {
/**
* #return string The type name
*/
function getName();
/**
* #return array The type mapping
*/
function getMapping();
}
So (somewhere), you would create the class (this could be better):
$myIndex = new MyIndex();
$myIndex->setTypes([
new MyFirstType(),
new MySecondType(),
//...
]);
$myIndex->create();
I hope this helps.

Changing display logic for a field in sugarcrm

I have the following situation: Contacts without a first or last name, in fact, they only have a email address.
I can work with these contacts fine, but when I use the listview anywhere (for instance to show all contacts from a company) there now is no way to click through to the contact (normally you would click on the name).
I'm looking for a way to solve this, for instance by showing a clickable text like 'name not known', but can't figure out how to do this. I've been looking at the manual and in the files in the modules directory and the sugarfields dir, but can't quite figure it out.
The closest I got was in /sugarcrm/modules/Contacts/metadata/listviewdefs.php
where this piece of code resides:
$listViewDefs['Contacts'] = array(
'NAME' => array(
'width' => '20%',
'label' => 'LBL_LIST_NAME',
'link' => true,
'contextMenu' => array('objectType' => 'sugarPerson',
'metaData' => array('contact_id' => '{$ID}',
'module' => 'Contacts',
'return_action' => 'ListView',
'contact_name' => '{$FULL_NAME}',
'parent_id' => '{$ACCOUNT_ID}',
'parent_name' => '{$ACCOUNT_NAME}',
'return_module' => 'Contacts',
'return_action' => 'ListView',
'parent_type' => 'Account',
'notes_parent_type' => 'Account')
),
'orderBy' => 'name',
'default' => true,
'related_fields' => array('first_name', 'last_name', 'salutation', 'account_name', 'account_id'),
),
Somewhere there has to be a function that joins the first and lastname together...
Edit: I found a solution:
The actual concatenation function is in /sugarcrm/include/SugarObjects/templates/person/person.php and is called _create_proper_name_field()
I can modify the output for my specific case by adding something like this to the end of the function:
if (empty(trim($full_name))){
$full_name = 'Name unknown';
}
However, I would rather have a upgrade safe solution, so that will be the next challenge.
Don't edit the core because the next upgrade will break your SugarCRM instance. Use logic hooks to be upgrade safe:
create a file 'logic_hooks.php' in /custom/modules/Contacts/
In that file, add the followin code:
<?php
$hook_array['before_save'][] = Array(1,'logic_fill_name','custom/modules/Contacts/logic_hooks/logics.php','ContactLogics','logic_fill_name');
After you have done this. create the file 'logics.php' in /custom/modules/Contacts/logic_hooks.
In the logics.php file, add something like:
<?php
require_once 'include/SugarQuery/SugarQuery.php';
/**
* Class ContactLogics
*/
class ContactLogics {
/**
* #param $bean
* #param $event
* #param $arguments
*/
public function logic_fill_name($bean, $event, $arguments) {
if (empty(trim($bean->first_name)) && empty(trim($bean->last_name))){
$bean->last_name = 'Name unknown';
}
}
}
Now some explanation. When you edited a recordview and pressed the save button, the logic hook 'before_save' will be triggered. This code will change the full name to 'Name unknown' when the full name is empty. When the 'before_save' is executed, the actual save will take place.

What schould be the output of Zend\Mvc\Router\RouteInterface::assemble() in ZF2

I'm writing my custom router for a Zend Framework 2 project extending Zend\Mvc\Router\RouteInterface. The routes should come from a database (large project with hundreds of pages). A working Router obviously only needs two methods: match() and assemble(). The match one I got working alright.
But what about assemble()? What should this method return? Could it be it only returns the base path of the Application?
Here is what one of the internal routers (Zend\Mvc\Router\SimpleRouteStack) of ZF2 does:
/**
* assemble(): defined by RouteInterface interface.
*
* #see \Zend\Mvc\Router\RouteInterface::assemble()
* #param array $params
* #param array $options
* #return mixed
* #throws Exception\InvalidArgumentException
* #throws Exception\RuntimeException
*/
public function assemble(array $params = array(), array $options = array())
{
if (!isset($options['name'])) {
throw new Exception\InvalidArgumentException('Missing "name" option');
}
$route = $this->routes->get($options['name']);
if (!$route) {
throw new Exception\RuntimeException(sprintf('Route with name "%s" not found', $options['name']));
}
unset($options['name']);
return $route->assemble(array_merge($this->defaultParams, $params), $options);
}
Reference: Custom Routing in Zend Framework 2
Basically assemble is what would be call when you do things like $this->redirect-toRoute($name, $params);
so it should return a URL string based on the route config. that the route can match using the same route config.
When you call toRoute the routestack that you posted finds the route with the name you specified in the call and then asks it to assemble the URL to that route
'test' => array(
'type' => 'Segment',
'options' => array(
'route' => '/test[/:id]',
'constraints' => array(
'id' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
),
),
),
this route named 'test' when we call $this->redirect-toRoute('test', array('id' => 1)); the route stack will find the instantiated route for 'test', this is a \Zend\Mvc\Router\Http\Segment and then calls it assemble function which will take the params send with in the call to toRoute and it will yield a URL string like this
/test/1
and that's basically what the assemble function does.

Adding custom columns to Propel model?

At the moment I am using the below query:
$claims = ClaimQuery::create('c')
->leftJoinUser()
->withColumn('CONCAT(User.Firstname, " ", User.Lastname)', 'name')
->withColumn('User.Email', 'email')
->filterByArray($conditions)
->paginate($page = $page, $maxPerPage = $top);
However I then want to add columns manually, so I thought this would simply work:
foreach($claims as &$claim){
$claim->actions = array('edit' => array(
'url' => $this->get('router')->generate('hera_claims_edit'),
'text' => 'Edit'
)
);
}
return array('claims' => $claims, 'count' => count($claims));
However when the data is returned Propel or Symfony2 seems to be stripping the custom data when it gets converted to JSON along with all of the superflous model data.
What is the correct way of manually adding data this way?
To export virtual columns to out array you can do it the next way:
/**
* Propel result set
* #var \PropelObjectCollection
*/
$claims = ClaimQuery::create('c')-> ... ->getResults();
/**
* Array of data with virtual columns
* #var array
*/
$claims_array = array_map(function (Claim $claim) {
return array_merge(
$claim->toArray(), // using "native" export function
array( // adding virtual columns
'Email' => $claim->getVirtualColumn('email'),
'Name' => $claim->getVirtualColumn('name')
)
);
}, $claims->getArrayCopy()); // Getting array of `Claim` objects from `PropelObjectCollection`
unset($claims); // unsetting unnecessary object if we have further operations to complete
The answer to this lies in the toArray() method so:
$claims = ClaimQuery::create('c')
->leftJoinUser()
->withColumn('CONCAT(User.Firstname, " ", User.Lastname)', 'name')
->withColumn('User.Email', 'email')
->filterByArray($conditions)
->paginate($page = $page, $maxPerPage = $top)->getResults()->toArray();
Then you can modify as required, the only issue here is that the current toArray method does not return virtual columns so you would have to patch the method to include them. (This is in the PropelObjectCollection class)
In the end I decided to separate the parts:
return array(
'claims' => $claims,
'count' => $claims->count(),
'actions' => $this->actions()
);
This way you do not have to worry about the virtual columns being lost and jut have to manipulate your data in different ways on the other end.

Categories