How to getData() from a Zend\Form before the validation in Zend Framework 2/3? - zend-framework2

I have a complex nested (order) Zend\Form, that can be edited multiple times. The user first creates an order, but doesn't need to place it immediately. He can just save the order (or more exact: its data) and edit it later. In this case the application loads an Order object (with all its nested structure) and binds it to the form. The important steps are:
get ID of the order from the request
get the Order object by ID
$orderForm->bind($orderObject)
...
Now I want to catch the data and serialize it to JSON. (The background: Forms cloning -- in the next step a empty new form should created and the should be passed to it; after saving we'll get a clone.) It should happen between 2 and 3. So I'm trying
$formData = $this->orderForm->getData();
$formJson = json_encode($formData, JSON_UNESCAPED_SLASHES);
and getting the error:
Zend\Form\Form::getData cannot return data as validation has not yet occurred
Well, I could try to work around it and validate the form:
$formIsValid = $this->orderForm->isValid();
but it only leads to further troubles:
Zend\InputFilter\BaseInputFilter::setData expects an array or Traversable argument; received NULL
Is there a way to get the form data before the validation?

Okay, the comment space is way too small to say everything about what you try to archive. Let 's refactor every single step you mentioned in the starting post. This will lead us to your goal. It 's all about hydration.
This will be a small example, how an order entity with products in it could look like. After the order entity follows the product entity, which we need for this example.
namespace Application\Entity;
class Order implements \JsonSerializable
{
/**
* ID of the order
* #var integer
*/
protected $orderID;
/**
* Array of \Application\Entity\Product
* #var array
*/
protected $products;
public function getOrderID() : integer
{
return $this->orderID;
}
public function setOrderID(integer $orderID) : Order
{
$this->orderID = $orderID;
return $this;
}
public function getProducts()
{
if ($this->products == null) {
$this->products = [];
}
return $this->products;
}
public function setProducts(array $products) : Order
{
$this->products = $products;
return $this;
}
/**
* #see \JsonSerializable::jsonSerialize()
*/
public function jsonSerialize()
{
return get_object_vars($this);
}
}
The following entity represents a product.
class Product implements \JsonSerializable
{
protected $productID;
protected $name;
public function getProductID() : integer
{
return $this->productID;
}
public function setProductID(integer $productID) : Product
{
$this->productID = $productID;
return $this;
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : Product
{
$this->name = $name;
return $this;
}
/**
* #see \JsonSerializable::jsonSerialize()
*/
public function jsonSerialize()
{
return get_object_vars($this);
}
}
Above you see our entity, wich represents a single order with several possible products in it. The second member products can be an array with Product entities. This entity represents the data structure of our simple order.
At this point we need a form, which uses this entites as objects for the data it contains. A possible factory for our form could look like this.
namespace Application\Form\Factory;
class OrderFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $serviceLocator->getServiceLocator();
$inputFilter = $parentLocator->get('InputFilterManager')->get(OrderInputFiler::class);
$hydrator = new ClassMethods(false);
$entity = new OrderEntity();
return (new OrderForm())
->setInputFilter($inputFilter)
->setHydrator($hydrator)
->setObject($entity);
}
}
This is the factory for our form. We set a hydrator, an input filter and an entity for the form. So you don 't have to bind something. The following code shows, how to handle data with this form.
// retrieve an order from database by id
// This returns a order entity as result
$order = $this->getServiceLocator()->get(OrderTableGateway::class)->fetchById($id);
// Extract the order data from object to array assumed the
// retrieved data from data base is an OrderEntity object
// the hydrator will use the get* methods of the entity and returns an array
$data = (new ClassMethods(false))->extract($order);
// fill the form with the extracted data
$form = $this->getServiceLocator()->get('FormElementManager')->get(OrderForm::class);
$form->setData($data);
if ($form->isValid()) {
// returns a validated order entity
$order = $form->getData();
}
It is absolutely not possible to get data from a form, that is not validated yet. You have to validate the form data and after that you can get the filtered / validated data from the form. Hydrators and entities will help you a lot when you have to handle a lot of data.

Related

Neo4j - Custom converter for field of type List

I am trying to write a custom converter for a nested object so that this object gets saved as string in Neo4j database.
I am using #Convert annotation on my field and passing ImageConverter.class which is my AttributeConverter class.
Everything works fine as expected and I am able to save string representation of Image class in Neo4j db.
However, now instead of single image I want to have List<Image> as my nested field. In this case, putting #Convert(ImageConverter.class) doesn't work.
I see that there is a class called ConverterBasedCollectionConverter which gets used when I have a field of type List<LocalDateTime.
However, I couldn't find any exammples on how to use this class in case of custom converters.
Please can anyone help me with this or if there is any other approach to use custom converter on field of type List.
I am using Neo4j (version 3.4.1) and Spring-data-neo4j (5.0.10.RELEASE) in my application. I am also using OGM.
PS: I am aware that it is advised to store nested objects as separate node establishing a relationship with parent object. However, my use case demands that the object be stored as string property and not as separate node.
Regards,
V
It is not so difficult as I assumed it would be.
Given a class (snippet)
#NodeEntity
public class Actor {
#Id #GeneratedValue
private Long id;
#Convert(MyImageListConverter.class)
public List<MyImage> images = new ArrayList<>();
// ....
}
with MyImage as simple as can be
public class MyImage {
public String blob;
public MyImage(String blob) {
this.blob = blob;
}
public static MyImage of(String value) {
return new MyImage(value);
}
}
and a converter
public class MyImageListConverter implements AttributeConverter<List<MyImage>, String[]> {
#Override
public String[] toGraphProperty(List<MyImage> value) {
if (value == null) {
return null;
}
String[] values = new String[(value.size())];
int i = 0;
for (MyImage image : value) {
values[i++] = image.blob;
}
return values;
}
#Override
public List<MyImage> toEntityAttribute(String[] values) {
List<MyImage> images = new ArrayList<>(values.length);
for (String value : values) {
images.add(MyImage.of(value));
}
return images;
}
}
will print following debug output on save that I think is what you want:
UNWIND {rows} as row CREATE (n:Actor) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-1, props={images=[blobb], name=Jeff}}]}
especially the images part.
Test method for this looks like
#Test
public void test() {
Actor jeff = new Actor("Jeff");
String blobValue = "blobb";
jeff.images.add(new MyImage(blobValue));
session.save(jeff);
session.clear();
Actor loadedActor = session.load(Actor.class, jeff.getId());
assertThat(loadedActor.images.get(0).blob).isEqualTo(blobValue);
}
I am came up with a solution to my problem. So, in case you want another solution along with the solution provided by #meistermeier, you can use the below code.
public class ListImageConverter extends ConverterBasedCollectionConverter<Image, String>{
public ListImageConverter() {
super(List.class, new ImageConverter());
}
#Override
public String[] toGraphProperty(Collection<Image> values) {
Object[] graphProperties = super.toGraphProperty(values);
String[] stringArray = Arrays.stream(graphProperties).toArray(String[]::new);
return stringArray;
}
#Override
public Collection<Image> toEntityAttribute(String[] values) {
return super.toEntityAttribute(values);
}
}
ImageConverter class just implements AttributeConverter<Image, String> where I serialize and deserialize my Image object to/from json.
I chose to go with this approach because I had Image field in one object and List<Image> in another object. So just by changing #Convert(ListImageConverter.class) to #Convert(ImageConverter.class) I was able to save list as well as single object in Neo4j database.
Note: You can skip overriding toEntityAttribute method if you want. It doesn't add much value.
However you have to override toGraphProperty as within Neo4j code it checks for presence of declared method with name toGraphProperty.
Hope this helps someone!
Regards,
V

zend framework 2 understanding the exchangeArray method

From the documentation:
namespace Album\Model;
class Album
{
public $id;
public $artist;
public $title;
public function exchangeArray($data)
{
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->artist = (!empty($data['artist'])) ? $data['artist'] : null;
$this->title = (!empty($data['title'])) ? $data['title'] : null;
}
}
Our Album entity object is a simple PHP class. In order to work with Zend\Db’s TableGateway class, we need to implement the exchangeArray() method. This method simply copies the data from the passed in array to our entity’s properties
Ok, we need to. But what's the pourpose of that function?
I mean, I've understood what that function does but I can't understand why it does things in that way.
Is it really necessary to declare all the variables?
Let's say I have a table of 20 columns and I want to select them all.
Then I should declare 20 named variables.
That makes sense if I want to distinguish between public (to print) and private (internal) variables.
Is there any other reason?
It 's not just about defining class members. It 's more about object orientated benefits like encapsulation, inheritance, etc.
Let 's assume your entity looks like this:
declare(strict_types=1);
namespace Application\Entity;
class Album
{
protected $id;
protected $artist;
protected $title;
public function getId() : int
{
return $this->id;
}
public function setId(int $id) : Album
{
$this->id = $id;
return $this;
}
public function getArtist() : string
{
return $this->artist;
}
public function setArtist(string $artist) : Album
{
$this->artist = $artist;
return $this;
}
public function getTitle() : string
{
return $this->title;
}
public function setTitle(string $title) : Album
{
$this->title = $title;
return $this;
}
}
First advantage using entities: there is no possibility to make typos. $data['atrist'] = 'Marcel' will work in most cases. $album->setAtrist('Marcel') will throw an error.
Second advantage is type hinting. Especially when you 're using PHP7 you can use the advantage of type hinting. $album->setId('1') will throw an error because this method expects an integer value.
Third advantage is the possibility of adding some extra code to your entity. what if we need a release date and no release date is given? You can kind of validate things in entities.
protected $releaseDate;
public function getReleaseDate() : \DateTime
{
if ($this->releaseData == null) {
throw new \Exception('no release date given. evacuate!');
}
return $this->releaseDate;
}
Another advantage is hydration in zend framework. Although the exchangeArray method is a kind of simple hydration, zend framework offers way more complex ways of hydration. What, if your release date column in the database table is of type DATE and you want your releaseDate member in your entity to be a \DateTime object representing this date?
// data from your database
$data = [
'id' => 1,
'artist' => 'the outside agency',
'title' => 'scenocide 202',
'releaseDate' => '2010-06-30',
];
// hydration of your entity with zend 's own hydrator classes
$album = (new ClassMethods())
->addStrategy('releaseDate', new DateTimeStrategy('Y-m-d'))
->hydrate($data, new Album());
$releaseDate = $album->getReleaseDate()->format('d.m.Y');
As you can see the release date was a simple string. While hydrating your entity, the release date will be transformed to a \DateTime object through a hydrator strategy.
These benefits are way more than distinguish between public, protected and private variables. An entity only takes and gives variables, that should be in your entity. You can use all the oo things like inheritance (implementing the \JsonSerializable interface is pretty magic sometimes), type hinting, encapsulation, polymorphism and so on ...
Last but not least: IDE support. If your entity object is strictly php doc commented, your IDE knows what you can do with your entity. Less work for you. ;)
Edit: Table Gateway instantiation with hydrating resultset
To use the above described advantges of entity objects with hydrators in a table gateway, you have to instantiate the table gateway like in the following example.
class AlbumTableGateway extends TableGateway
{
public function __construct(Adapter $adapter)
{
$resultset = new HydratingResultset(
(new ClassMethods())->addStrategy('releaseDate', new DateTimeFormatter()),
new AlbumEntity()
);
parent::__construct('album_table', $adapter, null, $resultset);
}
public function fetchById($id)
{
$select = $this->getSql()->select();
$select->columns([
'id',
'artist',
'title',
'releaseDate',
]);
$select->where->equalTo('id', $id);
$result = $this->selectWith($select);
// get the found resultset with $result->current()->getId();
return $result;
}
}
This example assumes that the Table Gateway is created via a corresponding factory.

Zend\Db Model with Child Models

ZF2 project - no Doctrine, using native Zend\Db: Have the following structure:
Controller
ProductController
Model
Product
ProductTable
ProductType
ProductTypeTable
Product is the model, has variables corresponding to the “products" table fields.
ProductTable is table class which is connected to the database via tableGateway. ProductTable has getItem() method to retrieve requested product by “id”.
ProductType is the model, has variables like id, name, description corresponding to the “productTypes" table fields.
ProductTypeTable is table class just like ProductTable.
Each product belongs to a certain ProductType
products.productTypeId = productTypes.id
is the relation.
In ProductTable->getItem() method, I can simply get productTypeId.
I can use joins to get productTypes.name, productTypes.description, or any field from "productTypes" table.
But I don’t want to do this - instead dealing with new variables in Product entity like productTypeName, productTypeDesc,
I’d like to have Product->getProductType() and set it to be a ProductType object, so I can get Product->getProductType() ->getName() to get product type name.
Simply I’d like to assign a child model as a variable of the parent model.
I can do this in the controller like below:
$product = $this->getProductTable()->getItem(7); // id = 7
$product->setProductType($this->getProductTypeTable()
->getItem($product->getProductTypeId());
But I’d like to make it happen in product table class getItem() method. So I don’t have to think about it in every controller, and it is kind of encapsulated.
What is the right way to do this?
Thank you.
The issue that you have is the Table Gateway pattern is only really any good at abstracting database access to a a single database table. It does not in anyway allow for the hydration of entities or management of relationships. Object Relationship Mappers (ORM's), such as Doctrine, solve this problem.
If Doctrine, for whatever reason, is inappropriate for your use case an alternative could be implementing the Data Mapper Pattern
The Data Mapper is a layer of software that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other
The data mapper will use the table gateway to fetch the required data for each table and construct the Product instance, including it's associated ProductType. You would then expose the mapper to the controller (rather than the table gateway).
A simple example of a ProductMapper.
class ProductMapper
{
// #var \Zend\Db\TableGateway\TableGateway
protected $productTable;
protected $productTypeMapper;
// an 'identity map' of loaded products
protected $loaded = [];
public function __construct(ProductTable $productTable, ProductTypeMapper $productTypeMapper)
{
$this->productTable = $productTable;
$this->productTypeMapper = $productTypeMapper;
}
protected function hydrate(Product $product, array $data)
{
$product->setId($data['id']);
$product->setName($data['name']);
$product->setFoo($data['foo']);
if (isset($data['type_id'])) {
// Load a fully constructed product type from the database
$type = $this->productTypeMapper->findById($data['type_id']);
$product->setType($type);
}
return $product;
}
public function findById($id)
{
if (isset($this->loaded[$id])) {
return $this->loaded[$id];
}
// Get the data
$row = $this->productTable->select(['id' => $id]);
if (empty($row)) {
throw new SomeCustomException("No product could be found with id $id");
}
// Create and hydrate the product
$product = $this->hydrate(new Product, $row->current())
$this->loaded[$id] = $product;
return $product;
}
public function save(array $data);
public function update($data);
public function delete($id);
}
You can achieve this, you just have to follow the following 3 steps:
Make your Product->exchangeArray() function smarter
Get all required ProductType fields, using a prefix helps for example: type_
Add #var ProductType so you will have proper autocompete (works for me in Eclipse)
<?php
namespace Product\Model\Product;
class Product {
public $id;
...
/**
* #var ProductType
*/
public $productType;
...
public function exchangeArray( $data ) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
...
$productType = new ProductType();
$typeData = array(
'id' => $data['type_id'],
'value' => $data['type_value']
);
$productType->exchangeArray( $typeData );
$this->productType = $productType;
}
}

return table primary key name with tableGateway

I'm trying to create an abstract object for my Table Objects.
Today I have lots of object like: CategoriaTable, FornecedoresTable, etc that implement $this->tableGateway->insert(), $this->tableGateway->update(), etc
I created an TableAbstract that contains most of those functionallities, but I stuck on one problem:
// In CategoriaTable my table id is named cat_id
$this->tableGateway->update($object->getArrayCopy(),array('cat_id' => $object->getId()))
// But in FornecedoresTable my table id is named for_id
$this->tableGateway->update($object->getArrayCopy(),array('for_id' => $object->getId()))
How can I get from tableGateway the id of an table? There is an better way to do what I want?
I guess I could inject the id name in my object but I don't thing this is a good way to do that...
You can create new TableGateway class parameter.(In my case I created $this->primary;)
And if it is not set use Zend\Db\Metadata\Metadata to find it straight from db structure.
<?php
//...
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Metadata\Metadata;
class AbstractTable extends AbstractTableGateway
{
protected $primary;
public function getPrimary()
{
if (null === $this->primary) {
$metadata = new Metadata($this->adapter);
$constraints = $metadata->getTable($this->getTable()->getTable())
->getConstraints();
foreach ($constraints AS $constraint) {
if ($constraint->isPrimaryKey()) {
$primaryColumns = $constraint->getColumns();
$this->primary = $primaryColumns;
}
}
}
return $this->primary;
}
}
?>

Linq to SQL using Repository Pattern: Object has no supported translation to SQL

I have been scratching my head all morning behind this but still haven't been able to figure out what might be causing this.
I have a composite repository object that references two other repositories. I'm trying to instantiate a Model type in my LINQ query (see first code snippet).
public class SqlCommunityRepository : ICommunityRepository
{
private WebDataContext _ctx;
private IMarketRepository _marketRepository;
private IStateRepository _stateRepository;
public SqlCommunityRepository(WebDataContext ctx, IStateRepository stateRepository, IMarketRepository marketRepository)
{
_ctx = ctx;
_stateRepository = stateRepository;
_marketRepository = marketRepository;
}
public IQueryable<Model.Community> Communities
{
get
{
return (from comm in _ctx.Communities
select new Model.Community
{
CommunityId = comm.CommunityId,
CommunityName = comm.CommunityName,
City = comm.City,
PostalCode = comm.PostalCode,
Market = _marketRepository.GetMarket(comm.MarketId),
State = _stateRepository.GetState(comm.State)
}
);
}
}
}
The repository objects that I'm passing in look like this
public class SqlStateRepository : IStateRepository
{
private WebDataContext _ctx;
public SqlStateRepository(WebDataContext ctx)
{
_ctx = ctx;
}
public IQueryable<Model.State> States
{
get
{
return from state in _ctx.States
select new Model.State()
{
StateId = state.StateId,
StateName = state.StateName
};
}
}
public Model.State GetState(string stateName)
{
var s = (from state in States
where state.StateName.ToLower() == stateName
select state).FirstOrDefault();
return new Model.State()
{
StateId = s.StateId,
StateName = s.StateName
};
}
AND
public class SqlMarketRepository : IMarketRepository
{
private WebDataContext _ctx;
public SqlMarketRepository(WebDataContext ctx)
{
_ctx = ctx;
}
public IQueryable<Model.Market> Markets
{
get
{
return from market in _ctx.Markets
select new Model.Market()
{
MarketId = market.MarketId,
MarketName = market.MarketName,
StateId = market.StateId
};
}
}
public Model.Market GetMarket(int marketId)
{
return (from market in Markets
where market.MarketId == marketId
select market).FirstOrDefault();
}
}
This is how I'm wiring it all up:
WebDataContext ctx = new WebDataContext();
IMarketRepository mr = new SqlMarketRepository(ctx);
IStateRepository sr = new SqlStateRepository(ctx);
ICommunityRepository cr = new SqlCommunityRepository(ctx, sr, mr);
int commCount = cr.Communities.Count();
The last line in the above snippet is where it fails. When I debug through the instantiation (new Model.Community), it never goes into any of the other repository methods. I do not have a relationship between the underlying tables behind these three objects. Would this be the reason that LINQ to SQL is not able to build the expression tree right?
These are non-hydrated queries, not fully-hydrated collections.
The Communities query differs from the other two because it calls methods as objects are hydrated. These method calls are not translatable to SQL.
Normally this isn't a problem. For example: if you say Communities.ToList(), it will work and the methods will be called from the objects as they are hydrated.
If you modify the query such that the objects aren't hydrated, for example: when you say Communities.Count(), linq to sql attempts to send the method calls into the database and throws since it cannot. It does this even though those method calls ultimately would not affect the resulting count.
The simplest fix (if you truly expect fully hydrated collections) is to add ToList to the community query, hydrating it.
Try adding another repository method that looks like this:
public int CommunitiesCount()
{
get { return _ctx.Communities.Count(); }
}
This will allow you to return a count without exposing the entire object tree to the user, which is what I think you're trying to do anyway.
As you may have already guessed, I suspect that what you are calling the anonymous types are at fault (they're not really anonymous types; they are actual objects, which you are apparently partially populating in an effort to hide some of the fields from the end user).

Resources