Can TableGateway Use Multiple Tables Zend Framework 2 - zend-framework

In my module.php file, I want to pass multiple table names via TableGateway class in Zend Framework but I cannot find any documentation on it, other than it being limited to one table. The phpdoc for this (TableGateway) class says an array can be passed but again, I am not sure if it accepts more than one table.
for example in Module.php:
'Application\Model\LoginModel' => function($sm) {
$table_gateway = $sm->get('LoginService');
$table = new LoginModel($table_gateway);
return $table;
},
'LoginService' => function($sm) {
$db_adapter = $sm->get('Zend\Db\Adapter\Adapter');
$result_set_prototype = new ResultSet();
$result_set_prototype->setArrayObjectPrototype(new Login());
return new TableGateway(array('admins', 'members'), $db_adapter, null, $result_set_prototype);
}
Is it possible to do this and have multiple tables referenced or bound like this, or is it only designed to allow one table for each instance?

No it is not. Table Gateway object is intended to provide an object that represents a table in a database. Array can be passed to the constructor, but if you pass it, you will get InvalidArgumentException. Please check this code
https://github.com/zendframework/zend-db/blob/release-2.8.2/src/TableGateway/TableGateway.php#L34,L35
Please look at again TableGateway purposed on documentation here
https://framework.zend.com/manual/2.4/en/modules/zend.db.table-gateway.html

Related

Trying to add Support For Index Usage on EFCore Spanner Provider

I'm writing a driver for EF Core for Spanner - In basic level it works and I can write Read and Write Queries that get's translated to Spanner SQL , executed and return results etc..
Now I'm trying to add Support For Read Query with Secondary Index.
Ultimately I'm trying to generate this SQL Query:
SELECT * FROM PostTags#{ FORCE_INDEX = PostTagsByTagId } WHERE TagId = 1
From This Linq:
var postTag = ctx.PostTags.WithIndex("PostTagsByTagId").Where(x => x.TagId == 1).FirstOrDefault();
I've added extension method as follow:
public static class SpannerIndexSupport
{
public static IQueryable<TSource> WithIndex<TSource>(this IQueryable<TSource> query, string indexName)
{
var methodDefinition = typeof(SpannerIndexSupport).GetTypeInfo().GetMethods().Single(m => m.Name == "WithIndex");
var method = methodDefinition.MakeGenericMethod(typeof(TSource));
var args = new[] { query.Expression, Expression.Constant(indexName) };
var expression = Expression.Call(null, method, args);
return query.Provider.CreateQuery<TSource>(expression);
}
}
And tried to write IAsyncQueryProvider to support it but couldn't find a way to make it work.
Any ideas Anyone?
In the official Spanner EFCore library (https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner), I would start by overriding VisitTable(TableExpression tableExpression) in SpannerQuerySqlGenerator:
https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner/Query/Sql/Internal/SpannerQuerySqlGenerator.cs
This will allow you to get a proof of concept going because you can directly influence the generated SQL text there.
Once that works, then you will want to make it proper.
I suppose there might be a few ways to make this work. The simplest might be to have some custom no-op method marker in the Linq expression tree and then register an IMethodCallTranslator to convert it either to a custom spanner specific Expression (whose Accept calls into SqlGenerator to generate the proper Sql) or possibly creating a SqlTranslatingExpressionVisitor to switch out the table expression to a custom one that allows the FORCE_INDEX.
Sorry I couldn't help more.
This is now supported in the official Entity Framework provider for Google Cloud Spanner. You can add this by adding a tag to the query like this:
var singersOrderedByFullName = context.Singers
// This will add the following comment to the generated query:
// `-- Use hint: force_index FullName`
// This comment will be picked up by the interceptor and an index
// hint will be added to the query that is executed.
.TagWith("Use hint: force_index FullName")
.OrderBy(s => s.FullName)
.AsAsyncEnumerable();
A full example can be found here: https://github.com/googleapis/dotnet-spanner-entity-framework/blob/main/Google.Cloud.EntityFrameworkCore.Spanner.Samples/Snippets/QueryHintSample.cs

REST Api with QueryParamAuth authenticator - Yii2

I'm trying to create rest api for my application to get the data in my android app. This is my controller
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\filters\auth\QueryParamAuth;
/**
* Tk103 Controller API
*/
class Tk103Controller extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Tk103CurrentLocation';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => QueryParamAuth::className(),
];
return $behaviors;
}
}
I added access_token column in my user table, implemented findIdentityByAccessToken() in User Model and calling this URL
http://localhost:7872/api/v1/tk103s?access-token=abcd
This is working great and returning data if and only if access_token matches with any single user in the table.
I checked QueryParamAuth class and found that QueryParamAuth::authenticate() returns $identity after successful authentication.
Currently this url is returning whole data of my table.
What I want is(after authentication):
Get user id/username of the requester.
Based on that id/username, the data related to him as per relations of tables in db. (currently whole rows are being returned but I want only few that are matching with the current requester/user)
I tried but didn't getting any clue to catch returned $identity of user after authentication.
And I know it is possible too to make this work. Help me out folks to create magic.
Get user id/username of the requester.
That user instance you did return within the findIdentityByAccessToken method should be accessible any where inside your app within Yii::$app->user->identity. And should hold all the attributes retreived from DB. here is a quick example of using it to check access within the checkAccess method of the ActiveController class:
public function checkAccess($action, $model = null, $params = [])
{
// only an image owner can request the related 'delete' or 'update' actions
if ($action === 'update' or $action === 'delete') {
if ($model->user_id !== \Yii::$app->user->identity->id)
throw new \yii\web\ForbiddenHttpException('You can only '.$action.' images that you\'ve added.');
}
}
Note that the checkAccess is by default an empty method that is manually called inside all the built-in actions in ActiveController. the Idea is to pass the action ID and the model instance to it just after retrieving it from DB and before modifying it so we can do extra checks. If you just need to perform checks by actions ID then yii\filters\AccessControl may be enough but inside checkAccess you are expecting to also get the model instance itself so it is important to note that when building your own actions or overriding existing onces. be sure to manually invoke it the same way it is done in UpdateAction.php or DeleteAction.php.
whole rows are being returned but I want only few .. matching with .. current requester/user
It depends on how your data is structured. You can override ActiveController's actions to filter results before outputting them, it can be handled in the related SearchModel class if you are using one or it can be handled in model. A quick tip may be by simply overriding the find method inside your model:
public static function find()
{
return parent::find()->where(['user_id' => Yii::$app->user->getId()]); // or Yii::$app->user->identity->id
}
Note that this works only when using ActiveRecord. Which means when using this:
$images = Image::find()->all();
The find method we just overriden will be filtered by default by always including that where condition before generating the DB query. Also note the default built-in actions in ActiveController are using ActiveRecords but if you are using actions where you are constructing the SQL queries using the Query Builder then you should manually do the filtering.
The same can be done if using ActiveQuery (maybe better explained here) by doing this:
public static function find()
{
$query = new \app\models\Image(get_called_class());
return $query->andWhere(['user_id' => Yii::$app->user->getId()]);
}

Zend Framework Structure for Datamapper, Controller and Model

I have just started with Zend Framework, so I have lots of questions about the structure.
I hope I can explain it properly, it's rather difficult.
Ok, I have done the Quickstart Tutorial and decided to use a similar structure for my first project.
So I have a Datamapper, a Model and the Database Table File.
I have created a Form where I can enter a some Information(Item) and upload Images with it.
I have 2 Datamapper (Item and Image) as well as 2 Models for them. I have an Item Controller, that one jumps into the ItemMapper. In the Datamapper I have a save Method. Please see below:
public function save(Application_Model_Item $item)
{
$data = array(
'item_title' => $item->getItemTitle(),
'item_description' => $item->getItemDescription(),
);
$this->getDbTable()->insert($data);
// Add Image Information ($imageData is Session Data)
$table = new Application_Model_DbTable_Image();
foreach ($imageData as $fileData)
{
$image = new Application_Model_Image($fileData);
$data = array(
'image_newname' => $image->getNewImageName(),
'image_thumbname' => $image->getImageThumbName(),
);
$table->insert($data);
}
Now the question I have.
I have Getter and Setter in my Model. Should everything what is in the Item Database Table be in 1 Model? Means everything about the Image should go to the Image Model since it is in a different Database Table?
Is it correct that I save the Information about the Image in the Item Mapper? Shoudl I not have a save() Method in the ImageMapper and save it in there? If so, how do I jump from the Item Mapper to the Image Mapper? Or would I first finish everything about the Item, return the ID to the Controller and than call the ImageMapper from the Controller?
I read something about "Fat Model Thin Controller". I had this all the time in my had, but I noticed that my Controller got pretty fat with just putting the Form together. I have about 5 Dropdown Fields which are depending Dropdowns. When I saw that I duplicating Code I decided to add this in a separate Function. So f.e. I have a Dropdown for counties. I wrote a Function which is also in my Controller so it looks like this:
public function getCounties ()
{
// List of Counties does not exist in Cache, read from DB
if(!$CountyList = $this->getFromCache('counties')){
$geolocationMapper = new Application_Model_GeolocationMapper();
$CountyDropdown = $geolocationMapper->createCountyDropdown();
// Save DB Result in Cache
$this->addToCache ('counties',$CountyDropdown);
return $CountyDropdown;
}
else{
// Return Country List from Cache
return $this->getFromCache('counties');
}
}
In my Add Function I use
// Assign Geo Info to Form
$CountyList = $this->getCounties();
$form->getElement('county')->setMultiOptions($CountyList);
The Edit Function than
$CountyList = $this->getCounties();
$form->getElement('county')->setMultiOptions($CountyList)->setValue($activeCounty)
all the Functions like getCounties () stay in the Controller or should it be moved to the GeolocationMapper? And if so, how would that be called up?
Should the Form be created in some Function so I would only call up something like createForm() ? I really have a lot of duplication (Add and Edit Function) and than Stuff comes from Database or Form was not Valid and it comes from Cache with a setValue. It just adds up when using dependable Dropdowns.
I know this are lots of questions, but I have the feeling it gets very messy, as a learner you are happy when it works, but I would also like to structure it in a proper way. I hope it all makes sense.
Maybe some of you have a few Tipps I could use. Thanks a lot for your help in advance.
There are quite a few questions here and most of the answers will be down largely to personal preference. With that caveat out of the way:
Should everything what is in the Item Database Table be in 1 Model?
I would say yes, although in general, try and think about it from the perspective of the models rather than the database structure. So all of the 'item' data goes in the Item model - the fact that this is all stored in one database table is irrelevant, since the mapper handles the translation from one to the other.
Is it correct that I save the Information about the Image in the Item Mapper?
It's not clear where $imageData comes from in your example, but I'd say the Item mapper should call the Image mapper if there is image data to save, e.g.:
public function save(Application_Model_Item $item)
{
$data = array(
'item_title' => $item->getItemTitle(),
'item_description' => $item->getItemDescription(),
);
$this->getDbTable()->insert($data);
// save image data if present
if (isset($item->image)) {
$imageMapper = new Yourapp_Mapper_Image();
$imageMapper->save($item->image);
}
return true;
}
[Should] all the Functions like getCounties () stay in the Controller or should it be moved to the GeolocationMapper? And if so, how would that be called up?
I don't see any reason for these functions to be in the controller. Depending on how comfortable you are with the Zend_Form component, one approach might be to write a custom Yourapp_Form_Element_Counties class that extends Zend_Form_Element_Select. You then move your logic from the getCounties function into the this class, so the form element itself is responsible for populating the options it presents. E.g.:
class Yourapp_Form_Element_Counties extends Zend_Form_Element_Select
{
public function getMultiOptions()
{
// load counties here and return an array in the format key -> value
}
}
Another approach, if you have a lot of location-related form elements, might be to create a GeoLocation Service class, which has a function for counties, cities etc. that returns the options.
Should the Form be created in some Function so I would only call up something like createForm()
It's not clear how much form stuff you are doing in the controller already apart from populating select options, so it's hard to answer this one. Here are the principles I generally follow when using Zend_Form:
Each form has its own class, extending Zend_Form (or Zend_Dojo_Form), which exists in application/forms and is named Myapp_Form_*
Each form class sets up its elements in the init() method (which is called automatically by Zend_Form's constructor for exactly this purpose).
If I find I need slight variations to the same form in different actions (e.g. in an add and edit action), I create an abstract base class for the form (e.g. Myapp_Form_Post_Base) which defines the elements, and then action-specific classes which extend it: Myapp_Form_Post_Add, Myapp_Form_Post_Edit and so on. These classes make any changes they need to the base form in their own init() method.
My actions then look something like this:
public function editAction()
{
$form = new Myapp_Form_Post_Edit();
if ($this->_request->isPost()) {
if ($form->isValid($this->_request->getPost()) {
// save data, set flash messge and redirect
}
}
$this->view->form = $form;
}
My only other piece of advice is to try and follow the approach which seems most logical to you. You might find it difficult to find definitive 'best practices' for a lot of this stuff since there are many different ways to do it all.
I have tried to follow your ideas as much as possible, so all the calls for the Counties, Towns and Postcodes are in the GeolocationMapper and the saving of the Image Data is in the ImageMapper.
You asked of how I create my Form right now, here an example from my init() of the Form for the Counties etc...
// Create Dropdown for Counties
$county = new Zend_Form_Element_Select('county');
$county->setLabel('Select a County')
->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description',
array('tag' => 'p', 'class'=>'description')),
'Label',
array('HtmlTag',
array('tag'=>'li','class'=>'county'))
))
->setRequired(false);
// Create Dropdown for Town
$town = new Zend_Form_Element_Select('town');
$town->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description', array('tag' => 'p', 'class' => 'description')),
'Label',
array('HtmlTag',array('tag'=>'li','class'=>'town'))
));
// Create Dropdown for Postcode
$postcode = new Zend_Form_Element_Select('postcode');
$postcode->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description', array('tag' => 'p', 'class' => 'description')),
'Label',
array('HtmlTag',array('tag'=>'li','class'=>'postcode'))
))
->setRegisterInArrayValidator(false);
In my Controller I than get the Elements and fill them:
$geolocationMapper = new Application_Model_GeolocationMapper();
$CountyOptions = $geolocationMapper->createCountyDropdown();
$form->getElement('county')->setMultiOptions($CountyOptions);
In my GeolocationMapper I have the Methods to build my Array of Counties:
/** ===========================================================================
* Get Counties
* #param
* #return Object? of Counties
* ========================================================================= */
public function getCountyList()
{
$table = $this->getDbTable();
$select = $table->select()->distinct()
->from(array('p' => 'geolocation'),'county')
->order('county');
$resultSet = $this->getDbTable()->fetchAll($select);
$entries = array();
foreach ($resultSet as $row)
{
$entry = new Application_Model_Geolocation();
$entry->setId($row->county)
->setCounty($row->county);
$entries[] = $entry;
}
return $entries;
}
/** ===========================================================================
* Create Array which will be used for Dropdown
* #param
* #return Array of Counties
* ========================================================================= */
public function createCountyDropdown()
{
// List of Counties does not exist in Cache, read from DB
if(!$CountyList = $this->getFromCache('counties'))
{
$CountyList = $this->getCountyList();
$Counties[''] = "Please choose";
foreach($CountyList as $value)
{
$Counties[str_replace(' ','_',$value->getCounty())] = $value->getCounty();
}
$CountyDropdown = $Counties;
// Save DB Result in Cache
$this->addToCache ('counties',$CountyDropdown);
return $CountyDropdown;
}else{
return $this->getFromCache('counties');
}
}
The Counties I read in my GeolocationMapper. The Towns and Postcodes get read when you choose a County, which than calls via Ajax the Geolocation Mapper and than createTownDropdown($county) and when a Town is choosen the same procedure but an Ajax call for loadPostcodes() and there createPostcodeDropdown($town).
Does this all sounds correct or any suggestions how I could improve this?
I am sorry but I would really like to add another question since I can't find an answer anywhere... I also have an Image Upload which works via Ajax and jQuery. When you choose an Image to upload, the Image gets straight displayed. For this I create dynamically an input Element with an image src. I have not found ny other way to add Images otherwise to Zend Form. Is there a possibility to add an Image to display the image as a normal ? It would be just a lot easier to have a img, since I would like to use jQuery Drag and Drop. Thanks so much for your help so far !!!

Checking for duplicate values in a Model class

I have a model, Entity, and I built an EntityMapper and an Entity class (I'm just learning to use Zend Framework and following the tutorials). The Entity class has a setName method, and what I want it to do is check if there is another "entity" in the DB with the same name, and in that case throw an exception or something.
So, If I understand correctly, DB calls should only be in the Mapper class. So, inside setName, should I do something like:
$entity = new Application_Model_EntityMapper();
if ($entity->checkDuplicateName($name, $this->_id))
$this->_name = $name;
else
throw new Exception(...);
return $this;
and put the code that actually does the query in a new method in the Mapper class? (the query should, of course, be different if the "entity" is new or if it has an id already, but that's not the point of my question).
I know I could do this in a couple of ways, but my goal is to adjust as much as possible to the conventions of the framework.
Since saving is a duty of the Mapperobject I'd add the validation to the save routine of your mapper class.
I didn't understand what respective duties your different classes have, so I'll explain mine:
-Application_Model_Entity is a pure struct for data, this class has no dependencies
-Application_Model_EntityMapper posses the right to speak with the dbrms, will transform Entities in Records and vice versa. It "owns" the ActiveRecord (DbTable) class
-Application_Model_DbTable_Entity is the ActiveRecord class, it extends from Zend_DbTable_Abstract and is capable of doing queries against the DB, it is only used by the Mapper.
$entity = new Application_Model_Entity();
$entity->setName('something which already exists');
$mapper = new Application_Model_EntityMapper();
$mapper->save($entity); // throws Exception
// works with:
class Application_Model_EntityMapper
{
/** #var Application_Model_DbTable_Entity */
private $dbTable;
...
public function save(Application_Model_Entity $entity)
{
$doValidation = ! $entity->getId(); // no id means not in db yet
if ( $doValidation )
{
$hasDuplicatesValidator = new Zend_Validate_Db_RecordExists(
'table' => 'entity',
'field' => 'name'
);
$hasDuplicates = $hasDuplicatesValidator->isValid($entity->getName());
if ( $hasDuplicates )
{
throw new Exception('There is already a record in the db with this name!');
}
}
// go on and save
$this->dbTable->save($entity);
}
}
I hope the code explains itself.
This is the most 'zendish' way i can find, hope this helps you on your way to the zf-community :)
Link to manual for Zend_Validate_*
I found that doing that check in setName causes it to run the query every time it loads a record from the db (not good), so I moved the call to checkDuplicateName to the save method of the Mapper class. (checkDuplicateName is also on the Mapper class, as a private method now)
I would still love to know if this is the standard way of doing this kind of stuff in Zend Framework.

Zend DB relationships

I am creating an application in zend framework. Now i am stuck in the Zend table relationships.
I have 2 tables. I have set the primary key and the corresponding references in other tables.
Now i want to join the two tables using the relationships (not with the join functions). Is it possible in zend?
the tables structures are like the one below
Schemetable
scheme_id primary key
Scheme_name
Scheme_Desc
Ratestable
rate_id
Scheme_id *foreign key ref scheme_id*
rates:
Time periods:
There is an one to many relation b/w the scheme and rates
I have done some coding in the model classes
Scheme.php
class Scheme extends Zend_Db_Table_Abstract {
protected $_name = 'schemetable';
protected $_dependentTables = array('rates');
}
Rates.php
class Rates extends Zend_Db_Table_Abstract {
protected $_name = 'ratetable';
protected $_referenceMap = array(
'Scheme' => array(
'columns' => array('scheme_id'),
'refColumns' => array('scheme_id'),
'refTableClass' => 'Scheme',
),
);
}
How can i fetch every scheme and their corresponding rates?
Thanks in advance.
Please,
see the DOCS:
http://framework.zend.com/manual/en/zend.db.table.relationships.html
Fetching a Dependent Rowset
If you have a Row object as the result
of a query on a parent table, you can
fetch rows from dependent tables that
reference the current row. Use the
method:
$row->findDependentRowset($table);
Example #4 Fetching Dependent Rowsets
using the Magic Method
This example shows finding dependent
Rowsets equivalent to those in the
previous examples. In this case, the
application uses the magic method
invocation instead of specifying the
table and rule as strings.
$accountsTable = new Accounts();
$accountsRowset = $accountsTable->find(1234);
$user1234 = $accountsRowset->current();
// Use the default reference rule
$bugsReportedBy = $user1234->findBugs();
// Specify the reference rule
$bugsAssignedTo = $user1234->findBugsByEngineer();
Fetching a parent row
If you have a Row object as the result
of a query on a dependent table, you
can fetch the row in the parent to
which the dependent row refers. Use
the method:
$row->findParentRow($table);
This example shows finding parent Rows
equivalent to those in the previous
examples. In this case, the
application uses the magic method
invocation instead of specifying the
table and rule as strings.
$bugsTable = new Bugs();
$bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
$bug1 = $bugsRowset->current();
// Use the default reference rule
$reporter = $bug1->findParentAccounts();
// Specify the reference rule
$engineer = $bug1->findParentAccountsByEngineer();