I'm very new to Zend Framework and php.
I went through the Zend Framework 2 tutorial and tried to use AbstractTableGateway for querying multiple tables.
But got the following message on the web page:
The table name of the provided select object must match that of the table
Here is part of my code:
class PublicationTable extends AbstractTableGateway
{
protected $table = 'publication';
public function fetchAll()
{
$sql = new Sql($this->adapter);
$select = $sql->select();
$select->from(array('p' => 'publication'))
->join('author','publication_fk=p.publication_pk');
$resultSet = $this->selectWith($select);
return $resultSet;
}
...
}
I'm aware that the variable "protected $table" is a String.
So how can one resolve this?
Thanks for the help!
EC
The from() method takes a table name, not a list of columns. Use columns() to specify the columns that you want. I've never tried from a TableGateway though as if you are doing joins, then TableGateway isn't the best pattern to follow.
If you use the DbAdapater directly, then something like this should work:
use Zend\Db\Sql\Select,
Zend\Db\ResultSet\ResultSet;
$select = new Select;
$select->from('publication')
->join('author', 'publication.publication_pk = author.publication_fk',
array('columnnamefromauthortable1', 'columnnamefromauthortable2'));
$statement = $adapter->createStatement();
$select->prepareStatement($adapter, $statement);
$resultSet = new ResultSet();
$resultSet->initialize($statement->execute());
Related
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.
I am with Laravel 5.1, I have this:
$q = Model::where('tipo','<>','');
then I do:
$res1 = $q->where('value','>',1)->get();
$res2 = $q->where('value','>',2)->get();
Then $res2 have inherit the where of $res1 too?
what can be the problem??
You should check Laravel Query Scope
Query Scope will allow you to easily re-use query. Add the query scope function inside your model. Simply prefix a model method with scope:
In Model:
public function scopeFirstCondition($query)
{
return $query->where('value','>',1);
}
public function scopeSecondCondition($query)
{
return $query->where('value','>',2);
}
Then you can fetch the values likewise:
$res1 = Model::firstCondition()->get();
$res2 = Model::secondCondition()->get();
If required you could also combine them:
$result = Model::firstCondition()->secondCondition()->get();
Docs: Laravel Query Scope
Hope this is Helpful.
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;
}
}
How can I create an entity that has other entities set? I use mapper/hydrator and hydrator srategy like this example.
class AccountMapperFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$dbAdapter = $serviceLocator->get('zfcuser_zend_db_adapter');
$entity = new Account();
$mapper = new AccountMapper();
$mapper->setDbAdapter($dbAdapter);
$hydrator = new AccountHydrator();
$hydrator->addStrategy('orderId', new OrderStrategy());
$mapper
->setEntityPrototype($entity)
->setHydrator($hydrator)
->setTableName($mapper->getTableName());
return $mapper;
}
}
Then i can call it like this
$this->AccountMapperFactory()->findById(1);
I get the account entity with an empty order entity, how can I create fill objects whats the way to do it?
Thank you,
For someone that is looking for the answer I found this two references.
retrieving-every-field-of-a-database-row-as-object-in-zend-framework-2
generate-entities-with-mappers
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;
}
}
?>