I am trying to serialize my object "ShoppingCart.php", and the object "Item.php", but i cant, and i dont know what else to do, so i leave here my code just to see if someone could help me. My problem is that the object ShoppingCart.php has a property which stores an array of Items, and i am not really sure if i am serializing/unserializing really well the object.
I have summarized my classes so they dont include all the methods, but they still show the most representatives. If still is not clear enought i will change it again.
Thanks in advance:
<?php
class ShoppingCart implements Iterator, Countable,Serializable {
// Array stores the list of items in the cart:
protected $items = array();
// For tracking iterations:
protected $position = 0;
// For storing the IDs, as a convenience:
protected $ids = array();
// Constructor just sets the object up for usage:
function __construct() {
$this->items = array();
$this->ids = array();
}
// Adds a new item to the cart:
public function addItem(Item $item) {
// Need the item id:
$id = $item->getId();
// Throw an exception if there's no id:
if (!$id) throw new Exception('The cart requires items with unique ID values.');
// Add or update:
if (isset($this->items[$id])) {
$this->updateItem($item, $this->items[$id]['qty'] + 1);
} else {
$this->items[$id] = array('item' => $item, 'qty' => 1);
$this->ids[] = $id; // Store the id, too!
}
} // End of addItem() method.
// Changes an item already in the cart:
public function updateItem(Item $item, $qty) {
// Need the unique item id:
$id = $item->getId();
// Delete or update accordingly:
if ($qty === 0) {
$this->deleteItem($item);
} elseif ( ($qty > 0) && ($qty != $this->items[$id]['qty'])) {
$this->items[$id]['qty'] = $qty;
}
} // End of updateItem() method.
// Removes an item from the cart:
public function deleteItem(Item $item) {
// Need the unique item id:
$id = $item->getId();
// Remove it:
if (isset($this->items[$id])) {
unset($this->items[$id]);
// Remove the stored id, too:
$index = array_search($id, $this->ids);
unset($this->ids[$index]);
// Recreate that array to prevent holes:
$this->ids = array_values($this->ids);
}
} // End of deleteItem() method.
public function serialize(){
foreach ($this->items as $clave => $item)
$listaItems[$clave]=serialize($this->items['item'][$clave]);
foreach ($this-items as $clave=>$valor)
$listaCantidades[$clave]=$this->items['qty'][$clave];
return
array(
'items'=>$listaItems,
'qty'=>$listaCantidades,
'ids'=>$this->ids,
);
}
public function unserialize($data){
//$data=unserialize($data);
$this->items=array(
'items'=>$data['items'],
'qty'=>$data['qty']
);
$this->ids=$data['ids'];
foreach($this->items as $clave => $item)
$this->items[$clave]=unserialize($item);
}
} // End of ShoppingCart class.
<?php # Item.php
// This is a sample Item class.
// This class could be extended by individual applications.
class Item implements Serializable{
// Item attributes are all protected:
protected $id;
protected $name;
protected $price;
protected $description;
public function serialize(){
return serialize(
array(
'id'=>$this->id,
'name'=>$this->name,
'price'=>$this->price,
'description'=>$this->description
)
);
}
public function unserialize($data){
$data=unserialize($data);
$this->id=$data['id'];
$this->name=$data['name'];
$this->price=$data['price'];
$this->description=$data['description'];
}
// Constructor populates the attributes:
public function __construct($id, $name, $price) {
$this->id = $id;
$this->name = $name;
$this->price = $price;
}
// Method that returns the ID:
public function getId() {
return $this->id;
}
// Method that returns the name:
public function getName() {
return $this->name;
}
// Method that returns the price:
public function getPrice() {
return $this->price;
}
public function getDescription() {
return $this->description;
}
public function setDescription($descripcion){
$this->description=$descripcion;
}
} // End of Item class.
Your immediate problem is that objects implementing Serializable must return a serialised string from the serialise method. Currently you're returning an array of data. You should be doing this instead:
public function serialize() {
...
return serialize(array(...));
}
You're actually doing this correctly in your Item class. However, I don't see why you're manually serialising in the first place. You're basically replicating the default behaviour of what serialize() already does without any added benefits. Just get rid of the Serializable interface and its implementation and the default behaviour of PHP's serialisation should serve you just fine. E.g.:
$_SESSION['cart'] = $myShoppingCart;
Done. No implementation of serialize necessary in your case.
Related
I don't understand how to pass the InvoiceItem objects instantiated in the ProcessInvoice class back to my array for output to the page.
See output at bottom of post. It should output all 3 items in the array and the grand invoice total.
Am I using the $this keyword wrong?? How to pass back the objects to the invoiceItems array in the Invoice class?
class InvoiceItem {
private $id;
private $itemQuanity;
private $itemPrice;
private $itemDescrip;
// Getters and Setter here for above 4 attributes
public function calculateItemTotal() {
$total = $this->itemPrice * $this->itemQuanity;
return $total;
}
public function display() {
echo "Item ID: $this->id // Item Quanity: $this->itemQuanity // Item Price: $this->itemPrice // Item Description: $this->itemDescrip // Total: $$this->total<br>";
}
}
class Invoice {
private $invoiceItems;
public function __construct() {
$item = new InvoiceItem();
$this->invoiceItems = array($item);
}
public function getInvoiceItems()
{
return $this->invoiceItems;
}
public function setInvoiceItems($invoiceItems)
{
$this->invoiceItems = $invoiceItems;
return $this;
}
public function calculateInvoice() {
foreach ($this->invoiceItems as $item_y) {
$invoiceTotal = $invoiceTotal += $item_y->calculateItemTotal();
}
echo "Invoice Total: $$invoiceTotal ";
}
public function displayInvoice() {
foreach ($this->invoiceItems as $item_x) {
$item_x->display();
}
return $this->calculateInvoice();
}
}
class ProcessInvoice {
private $invoice;
public function __construct() {
$this->invoice = new Invoice();
}
public function getInvoice()
{
return $this->invoice;
}
public function setInvoice($invoice)
{
$this->invoice = $invoice;
return $this;
}
function createInvoiceItems() {
$item1 = new InvoiceItem();
$item1->setId(1);
$item1->setItemPrice(1.66);
$item1->setItemQuanity(2);
$item1->setItemDescrip("item example");
$item2 = new InvoiceItem();
$item2->setId(2);
$item2->setItemPrice(34.99);
$item2->setItemQuanity(1);
$item2->setItemDescrip("bla bla");
$item3 = new InvoiceItem();
$item3->setId(3);
$item3->setItemPrice(2.24);
$item3->setItemQuanity(1);
$item3->setItemDescrip("Another item");
// SOMETHING WRONG HERE?!
$this->invoice->setInvoiceItems($item1, $item2, $item3);
}
function runProcess() {
$invoice_x = new Invoice();
$this->createInvoiceItems();
$invoice_x->displayInvoice();
}
}
Output: (from a "test drive" class not in this post - Simply calls the runProcess() method in the ProcessInvoice class)
Item ID: // Item Quanity: // Item Price: // Item Description: // Total: $
Invoice Total: $0
If Invoice::$invoiceItems is an array, you need to add invoices to it, you are just overwriting the property.
Create:
public function addInvoiceItem(InvoiceItem $invoiceItem)
{
$this->invoiceItems[] = $invoiceItem;
return $this;
}
And if you need to add several, you call the same method several times:
$this->invoice->addInvoiceItem($item1)
->addInvoiceItem($item2)
->addInvoiceItem($item3);
Additionally, your constructor doesn't appear to make a lot of sense. Why are you creating an array with an empty InvoiceItem?
Better just do this, and start with a properly empty array:
private $invoiceItems = [];
Here is my function for calling product attribute collection I have already get product attributes for the product's which are enabled but I am having issue in filtering them according to their own visibility i.e I want only those product attributes collection whose status is set visible from admin....
class ProductList extends \Magento\Framework\View\Element\Template
{
protected $_attributeFactory;
public function __construct(
\Magento\Catalog\Model\ResourceModel\Eav\Attribute $attributeFactory
){
parent::__construct($context);
$this->_attributeFactory = $attributeFactory;
}
public function getallattributes()
{
$arr = [];
$attributeInfo = $this->_attributeFactory->getCollection()->addFieldToFilter(\Magento\Eav\Model\Entity\Attribute\Set::KEY_ENTITY_TYPE_ID, 4);
foreach($attributeInfo as $attributes)
{
$attributeId = $attributes->getAttributeId();
// You can get all fields of attribute here
$arr[$attributes->getAttributeId()] = $attributes->getFrontendLabel();
}
return $arr;
} }
No tested but it will do job for you
$attributeInfo = $this->_attributeFactory->getCollection()
->addFieldToFilter(\Magento\Eav\Model\Entity\Attribute\Set::KEY_ENTITY_TYPE_ID, 4)
->addFieldToFilter('is_visible_on_front',1);
To get All product attributes you need to use inject class
app/code/Mendor/Mymodule/Model/Attributes.php
public function __construct(Context $context,
\Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection $coll
){
$this->_coll=$coll;
parent::__construct($context);
}
public function getAllAttributes()
{
$this->_coll->addFieldToFilter(\Magento\Eav\Model\Entity\Attribute\Set::KEY_ENTITY_TYPE_ID, 4);
$attrAll = $this->_coll->load()->getItems();
echo '<pre>'; var_dump($attrAll);'</pre>';
exit;
}
You can do it using the following function:
public function getallattributes()
{
$arr = [];
$attributeInfo = $this->_attributeFactory->getCollection()-
>addFieldToFilter(\Magento\Eav\Model\Entity\Attribute\Set::
KEY_ENTITY_TYPE_I D, 4);
foreach($attributeInfo as $attributes)
{
$attributeId = $attributes->getAttributeId();
// You can get all fields of attribute here
if($attributes->getIsVisibleOnFront()){
$arr[$attributes->getAttributeId()] = $attributes
>getFrontendLabel();
}
}
return $arr;
}
I'm trying to persist a domain model to a DB without using an ORM just for fun.
It's pretty easy to persist properties, but I'm having hard time persisting a collection.
Let's say I have the following two objects.
class aModel
{
private $items = [];
public function __construct($id, $name, array $items = [])
{
$this->id = $id;
$this->name = $name;
$this->items = $items;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function addItem(Item $item)
{
$this->items[] = $item;
}
}
class aDBRepository
{
public function persist(aModel $aModel)
{
$attributes = [
'id' => $aModel->getId(),
'name' => $aModel->getName()
];
$this->table->insert($attributes);
}
}
// Code
$aModel = new aModel("test id", "a name");
$aModel->addItem(new Item("id", "name"));
When I create a new aModel and add a new item to it, how do I detect 'unsaved' items and persist them?
I can only think of adding isSaved method in the Item class and loop through $items variable in aModel.
Without using reflection, what's the best way?
I implemented a little IdentityMap into my DataMappers and it works correctly in the way that it knows if an object has already been loaded but it does not assign the in memory object properly.
I have simplified the code down as much as I can (it's not complicated anyway) to one entity, no database etc. Could someone please explain why in the lookup() method does not properly assign the already loaded Customer object to the passed in Customer object?
Customer.php
class Customer {
private $id;
private $name;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
CustomerMapper
class CustomerMapper {
private $identityMap;
public function __construct(IdentityMap $identityMap) {
$this->identityMap = $identityMap;
}
public function fetch(Customer $customer) {
if( $this->identityMap->lookup($customer) ) {
return true;
}
$this->assign($customer, array('id' => 1, 'name' => 'John'));
}
private function assign(Customer $customer, Array $row) {
$customer->setId($row['id']);
$customer->setName($row['name']);
$this->identityMap->add($customer);
}
}
IdentityMap
class IdentityMap {
private $customers;
public function lookup(Customer $customer) {
if( !array_key_exists($customer->getId(), $this->customers) ) {
return false;
}
$customer = $this->customers[$customer->getId()]; //Something wrong here?
return true;
}
public function add(Customer $customer) {
$this->customers[$customer->getId()] = $customer;
}
}
When I then run this:
$identityMap = new IdentityMap();
$customerMapper = new CustomerMapper($identityMap);
for( $i = 0; $i < 3; $i++ ){
$customer = new Customer();
$customer->setId(1);
$customerMapper->fetch($customer);
echo 'ID: ' . $customer->getId() . '<br>Name: ' . $customer->getName() . '<br><br>';
}
Output:
ID: 1
Name: John
ID: 1
Name:
ID: 1
Name:
Why do the second and third Customer objects have no name? I am fairly sure there is a problem in the assigning part in the lookup() method. I have been at this since last night trying and reading everything.
I have changed the lookup() methods signature to having the "&" symbol in front of the passed in object with no luck.
The problem is
When fetch() is called In the first loop and it in turn calls lookup() it won't find any value (since identityMap is empty) as a result $customer will be given new value in assign() (in this case $customer->name = 'John' and $customer->id='1'). Here note that id is not provided by $customer->setId(1);. Whatever value you give $this->assign() modifies the original id value of $customer(passed by reference) by assigning id value to 1. You can test it by changing 1 to any arbitrary value (BTW if you change 1 to 3 for example, you will see all the results).
So in the first loop $customer is populated with all the values to display properly (id->1 and name->'john')
But in the second loop
if( $this->identityMap->lookup($customer) ) {
return true;
}
returns true. (a customer object with id =1 is found in $identityMap; so it is not modifying the $customer object passed as an argument.)
which means, the function returns before name value is assigned to $customer.
So starting from the second loop
for( $i = 0; $i < 3; $i++ ){
...
$customer->setId(1);
...
}
the newly created $customer objects will not be assigned name value. That's why it is displayed with id values only.
You can solve the above problem by applying the following changes:
function lookup(){
...
return $customer; // instead of returning true
}
function fetch(){
...
$c=$this->identityMap->lookup($customer);//
if($c){
$customer->name=$c->getName();
}
// if you like the new objects hold their original value do the following
$this->assign($customer, array('id' => $customer->getId(), 'name' => 'John'));
You are adding 3 customers to the lookup with the same key (id)
after the first for loop run the fetch method returns true for the rest of the for loop runs.
so the name will never be set.
You could try this:
if( $this->identityMap->lookup($customer) ) {
return $this->identityMap->get($customer);
}
But dont forget to implement the method "get" in the IdentityMap Class ;)
I have a problem to get an object from an array-collection of objects by ID.
The following is my code:
protected $_rootLocation;
public function __construct(Location $rootLocation)
{
$this->_rootLocation= $rootLocation;
var_dump($this->_rootLocation);
}
public function getLocationById($id)
{
$value = null;
foreach($this->_rootLocationas $root)
{
if ($id == $root->getId())
{
$value = $root;
break;
}
}
return $value;
}
Then the function return "NULL" so it's dosn't work...
Edit
I do like that :
$manager = new LocationManager($rootLocation);
echo "<pre>";
var_dump($manager->getLocationById('291'));
echo "</pre>";
Your function returns null because the object is not found!
It depends on the implementation of the myClasse object, this must implement the iterator interface and the getId() method must return a valid Id on each iteration.
Imagine that none of all objects in the array has the ID you're looking for. Your function will just return null. For example with an empty array.
As you can see, returning null does not mean that the function does not work. It works perfectly and did what you specified, it is just, that no such object exists.
It's then up to you to decide what to do if this happens. As you've not told in your question, there is not much to add but to offer you some options:
You could check if the function returns null and then take it as a "not found" case.
$result = $collection->getObjectById($id);
if (null === $result) {
# object not found
} else {
# object found
}
You can throw an Exception inside the function if the function should only be called for existing objects:
public function getObjectById($id) {
foreach ($this->_rootObject as $root) {
if ($id == $root->getId()) {
return $root;
}
}
throw new InvalidArgumentException(sprintf('Not a valid ID: %d', $id));
}
or finally:
Offer an additional function to check for an existing ID first:
private function findById($id) {
foreach ($this->_rootObject as $object) {
if ($id == $object->getId()) {
return $object;
}
}
return null;
}
public function hasObjectById($id) {
return null !== $this->findById($id);
}
public function getObjectById($id) {
if (null !== $root = $this->findById($id)) {
return $root;
}
throw new InvalidArgumentException(sprintf('Not a valid ID: %d', $id));
}
Also you might be interested to create yourself a class called that encapsulates your needs, so you do not need to implement that in your "I manage the root collection object " object which is more than indirect. This then is basically your own collection class. An example:
interface Identifiable {
public function getId();
}
/**
* Example Object Class
*/
class MyObject implements Identifiable {
private $id;
public function __construct($id) {
$this->id = (int) $id;
}
public function getId() {
return $this->id;
}
}
/**
* Example Collection
*/
class IdentifiableCollection implements Countable, IteratorAggregate
{
private $objects;
public function attach(Identifiable $object) {
$id = $object->getId();
$this->objects[$id] = $object;
}
public function count() {
return count($this->objects);
}
public function has($id) {
return isset($this->objects[$id]);
}
public function getById($id) {
if ($this->has($id)) {
return $this->objects[$id];
}
throw new InvalidArgumentException(sprintf("No object is identifiable for %d", $id));
}
public function getIterator() {
return new ArrayIterator($this->objects);
}
}
// create the collection
$collection = new IdentifiableCollection();
// fill the collection with some objects (ID 1 - 20)
foreach(range(1, 20) as $id) {
$collection->attach(new MyObject($id));
}
// test if an id exists and return object
$id = 2;
var_dump($collection->has($id), $collection->getById($id));
// iterate over the collection
foreach ($collection as $object) {
var_dump($object);
}
This collection class only offers to attach objects, not remove them but you can extend that as needed. It's also possible to extend from an existing class like ArrayObject or SplObjectStorage if you want to reuse existing functionality. An example is given in another answer in a somewhat related question:
Array of objects within class in PHP