Getting Form Data in edit-view with JTable which is joined? - json

i'm stuck with a problem of joined tables and retrieving Form-Data how it "should be" in best-practise-terms of Joomla!
I'm following the Joomla!-standards as far as my knownledge reaches, and the goal is to write this component "as if" it was native Joomla!-Code.
So what i have is the following COM_COMPONENT\models\release.php
<?php
defined('_JEXEC') or die;
class DojoMusicLibraryModelRelease extends JModelAdmin
{
public function getTable( $type = 'Releases', $prefix = 'DojoMusicLibraryTable', $config = array() )
{
return JTable::getInstance($type, $prefix, $config);
}
public function getForm( $data = array(), $loadData = true )
{
$options = array('control' => 'jform', 'load_data' => $loadData);
$form = $this->loadForm('releases', 'release', $options);
if (empty($form)) {
return false;
}
return $form;
}
protected function loadFormData()
{
$app = JFactory::getApplication();
$data = $app->getUserState('com_dojomusiclibrary.edit.release.data', array());
if (empty($data)) {
$data = $this->getItem();
}
return $data;
}
}
And in COM_COMPONENT\tables\releases.php
<?php
defined('_JEXEC') or die;
class DojoMusiclibraryTableReleases extends JTable
{
public $id;
public $title;
public $alias;
public $artist_id;
public $release_date_digital;
public $release_date_physical;
public $ean;
public $catalog_number;
public $promotional_text;
public $is_compilation;
public $format_id;
public $release_status;
// TODO: This tracklist should not be in this table, but only an id referring to it
public $tracklist;
public $created_at;
public $modified_at;
public $state;
public $publish_up;
public $publish_down;
public function __construct($db)
{
parent::__construct('#__dojomusiclibrary_releases', 'id', $db);
}
}
So now, as you can see from the comment in the latter code-example, the variable $tracklist is a field in the releases-table in my MySQL by now. As i got a "repeatable"-field-type, there is JSON inside of that field, and it works so far.
But the component is meant to hold another MySQL-table called "tracks", which holds all tracks of all releases and should be joined by a tracklist-id to the releases table, so that we have the following three tables:
releases (holds all the data, that is strictly bound to a single release/album/ep...)
tracklists (is an 1-to-m relation table, that has a tracklist_id, which is joined with the release and joins all single track_ids that belong to the tracklist)
tracks (holds all the track data, such as track_title, duration, genre and so on, while every track has an unique id, which can be joined to the tracklists)
As you can see this is getting more and more complex (especially if you consider that this is not the only part of the component, where this kind of joining tables for a single JForm will be needed).
Excerpt from the COM_COMPONENT\models\forms\release.xml
<!-- CATALOG NUMBER -->
<field name="catalog_number" type="text"
label="COM_DOJOMUSICLIBRARY_FORM_FIELD_CATALOG_NUMBER_LABEL"
description="COM_DOJOMUSICLIBRARY_FORM_FIELD_CATALOG_NUMBER_DESC" />
So now, as JForm seemingly expects something coming from the JTable since the release.xml binds the field-name to the naming of the variable in the JTable-Class, i do not really know how to deal with that, given that the data, that should be passed in one Form is coming from different tables.
So in summary i got different problems as far as i can see:
How can i manage to join tables for (best practice and right)
treatment of the JForm Standard in Joomla! ?
Since i use a repeatable-field-type to manage the tracklist, data will be stored into a JSON and saved to the database in only one field.
I need this repeatable solution since every release has n tracks with more than one information (track_no, title, genre...) and thanks to Joomla there is finally a native way to handle such cases.
BUT: Before saving them to the database the JSON must be split up into it's single values and be assigned to the proper fields in the tracks-table.
Okay... I know this is a huuuuuge question maybe... but since i'm totally stuck, I'd be happy about any advice for at least one of the issues :D
Thanks in advance :)

Related

Join subquery in Laravel model

Please suggest a better title for this question. I have problems to name my question properly.
Background
I'm creating a comic database for personal use, to track my comic reads. Every comic belongs to a series. Every comics has a release date. The release date of a series is the first release of the according comic. I have a eloquent function seriesByDate() for that:
class Series extends Model
{
public $timestamps = false;
protected $primaryKey = 'series_id';
protected $fillable = ['series_name', 'publisher_id'];
public function publisher()
{
return $this->belongsTo(Publisher::class, 'publisher_id');
}
public function comics()
{
return $this->hasMany(Comic::class, 'series_id', 'series_id');
}
// instead of saving the release date of a complete series
// we look for the first comic in this series and get the
// comic's release date.
public static function seriesByDate()
{
$firstRelease = DB::table('comics')
->select('series_id', DB::raw('MIN(comic_release_date) as first_release'))
->groupBy('series_id');
$seriesByDate = DB::table('series')
->leftJoinSub($firstRelease, 'first_release', function ($join) {
$join->on('series.series_id', '=', 'first_release.series_id');
})
->join('publishers', 'publishers.publisher_id', '=', 'series.publisher_id')
->select('series.series_id', 'series.series_name', 'first_release', 'publishers.publisher_name')
->get();
return $seriesByDate;
}
What i want
I want the release_date somehow be permanent to my Series model. Meaning: When I do a App\Series::all() i already want to have the release_date as a column in my returned data. Similar to App\Series::with('publishers')->get()
With my solution above i have to eplicitly execute App\Series::seriesByDate()
Is this even possible? Can you please give me a hint?
Edit / Update
The linked video by #Musa shows how to properly do this in a model: https://stackoverflow.com/a/61558482/5754486
You can't. There is no magic for this. You might eventually write your own custom Relation but that would be unnecessarily complex, just for the sake of having a pretty related/accessor. Both solutions are not great performance-wise.
Not sure why you choose such a structure. Without any further context/explanation, I would strongly recommend you to have a release_date column directly inside your Series model as well. That will be waaaay faster than your current structure.
If you still want to stick with that structure, I would personally retrieve the release_date "php side" instead of "database side" :
$series = App\Series::query()
->with([
'publishers',
'comics' => function ($query) {
$query->orderBy('created_at');
},
])
->get();
foreach ($series as $serie) {
$serieTitle = $serie->title;
$releaseDate = $serie->comics->first()->created_at;
echo $serieTitle.' was first released '.$releaseDate->diffForHumans().'<br/>';
}
(not tested)
the only downside is that it will return a Collection of every "comics" a "serie" has. If you do not have 10k comics per serie and you do not load 1k serie per page, that should be fine. In any case, this looks more elegant and optimized/faster than your seriesByDate method.
edit: also, you should watch "Advanced Querying With Eloquent" by Jonathan Reinink, at Laracon 2018 I believe. He discusses subqueries like the one you need. I am 100% sure you will find the best and most optimal Eloquent subquery one can forge for what you are trying to achieve : https://vimeo.com/showcase/7060635/video/255049572
you can defined an accessor then append the value
class Series extends Model
{
protected $appends = ['series_date'];
public function getSeriesDateAttribute()
{
return self::seriesByDate();
//OR build 'seriesByDate' manually, returning whatever you like.
}
}

Expanding this method to write to the database

Hi I followed a tutorial to implement a friend system. It all works find, but I need to post other columns to the row that just the id's. How would I expand that.
This is the method that is accessed when the add friend button is clicked
public function getAdd($id){
$user = User::where('id', $id)->first();
//After passing all checks. Add other account
Auth::user()->addFriend($user);
echo "Sent";
}
AddTenancy Method
public function addFriend(User $user){
$this->friendsOf()->attach($user->id);
}
I assume the relationship is many-to-many between users. And you need to add additional data to the pivot.
Here's how you'd do that:
public function addFriend(User $user){
$this->friendsOf()->attach($user->id, ['another_col' => 'some data']);
}
Replace 'another_col' and some data with your column and your data. You can also add more than 1 column into the array.

Joining Two Tables to a Reference Table Laravel

I Have three tables
#1 Table timeline which is my reference table with an Auto incremented ID which is stored in column id
#2 timeline_videos
#3 timeline_else
What happens is on post if a video is uploaded with the post
it will go into Table #2 ,anything else goes into Table #3.
#2-3 have the Auto Increment Id from the Table timeline stored in a column pid
On query of The Timeline I need to join both tables data using id=pid
so I can use the rest of the Relational Data with the post.
I have done a bit of research and can't seem to find a method for doing so.
So far the code I have
Controller
$groupposts = timeline::where([
['owner','=',$owner],['id','<',$lastid],
])
->join('timeline_videos','timeline.id','=','timeline_videos.pid')
//->join('timeline_else','timeline.id','=','timeline_else.pid')
->orderBy('id','desc')
->limit(5)
->get();
This works with no errors with the second Join commented out but I need to also grab the timeline_else data .
Update --
I have now decided to use Eloquent Relationships to join the tables,
my question now is what type of relationship do I have between the
tables?
I realize it basically needs to be able to switch between two tables to
grab data based on the fact that timeline_videos and timeline_else will not be "JOIN" but separated by type .
The tables need to Join with table #1 timeline based on a column I now have named type for clarifying where to look and matching/joining using the id = pid
You can use relationships.
it sounds like timelines has many videos and has many video failures
https://laravel.com/docs/5.5/eloquent-relationships#one-to-many
you would have a model for each table and set up the relationships
timelines model:
public function videos()
{
return $this-> hasMany('App\Videos');
}
public function videoFailures()
{
return $this-> hasMany('App\videoFailures');
}
videos model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
videos failures model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
You can then go:
$timeLine = Timmeline::find($id);
to find videos of the time lines you would do:
$videos = $timeLine->videos();
to find else:
$videoElse = $timeLine-> videoFailures();
By using some of what Parker Dell supplied and a bit more trial and error
My Models Looks like
timeline
class timeline extends Model
{
protected $table ='timeline';
public $timestamps = false;
public function videos()
{
return $this->hasMany('App\timeline_videos','pid','id');
}
public function else()
{
return $this->hasMany('App\timeline_ect','pid','id');
}
}
timeline_ect.php ,I changed the name of the table
class timeline_ect extends Model
{
protected $table='timeline_ect';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\Models\timeline','pid','id');
}
}
timeline_videos
class timeline_videos extends Model
{
protected $table='timeline_videos';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\timeline','id','pid');
}
}
Then Lastly my Controller
$timeline = timeline::with('videos','else')
->orderBy('id','desc')
->limit(5)
->get();
So far no Problem query is correct.

Table name having additional underscores added

I have an API that grabs data from a table, serialises the data and then sends back via the service. The code is written using CakePHP using MySQL.
When I pass in the table name (such as RX_Scaled), an error is being returned that the table r_x_scaled can't be found (which don't surprise me, the table name in the database is rx_scaled).
My Model/Table for rx_scaled is defined like this
class rx_scaleds extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('rx_scaled');
}
With the Model/Entity
class rx_scaled extends Entity
{
}
My service API is set like this within the APIController.php file
public function getData($tablename, $id="", $filter = "-", $order = "-", $take = 0)
{
$the_table = $tablename;
$this->autoRender = false;
$table = TableRegistry::get($tablename);
$data = null;
switch (strtolower($the_table))
{
case "rx_scaled":
$data = $table->find();
echo $data;
break;
}
(this is truncated)
The odd thing is that this error does not occur in all tables.
I'm obviously not doing something correctly, but I'm not sure what
You named your table object rx_scaleds but then you pass RX_Scaled to the getData action
cake try not finding a Table Object named RX_Scaled try to inflect the name of the mysql table: so using cake's conventions RX_Scaled is mapped to r_x_scaled
So what can you do?
Use cake conventions
Name you table RxScaledsTable
class RxScaledsTable extends Table
Name you entity RxScaled
class RxScaled extends Entity
and pass the string 'RxScaled' to your action

Yii model: Dynamic table relations

Table.linkedIndex is related to LinkedIndex.ID. The value of the field LinkedIndex.TableName is either Linked1 or Linked2 and defines which of these tables is related to a row in Table.
Now i want to make a dynamical link with Yii models so that i can easily get from a Table row to the corresponding Linked1 or Linked2 row:
Table.linkedID = [LinkedIndex.TableName].ID
Example
Table values:
LinkedIndex values:
Now I should get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
Model
In the model Table, I tried to make the relation to the table with the name of the value of linkedIndex.TableName:
public function relations()
{
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
'linked' => array(
self::HAS_ONE,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
)
}
But then I get the error:
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
Is there any way to make a dynamic relation Table.linkedID -> [LinkedIndex.TableName].ID with Yii Models?
Per the Yii docs here:
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail
I'd suggest using self::HAS_ONE instead (unless there can be multiple rows in LinkedIndex with the same ID - although from the looks of above, I doubt that's the case).
You can link tables together that have different keys by following the schema:
foreign_key => primary_key
In case you need to specify custom PK->FK association you can define it as array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').
so in your case:
public function relations(){
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
);
}
where LinkedIndex is the class name for the LinkedIndex model (relative to your Table model - i.e. same folder. You could change that, of course) and array('ID' => 'linkedIndex') specifies the relationship as LinkedIndex.ID = Table.linkedIndex.
Edit
Looking at your updated example, I think you're misunderstanding how the relations function works. You're getting the error
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
because you're trying to create another relation here:
'linked' => array(
self::BELONGS_TO,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
This part: linkedIndex.TableName refers to a new model class linkedIndex.TableName, so Yii attempts to load that class' file linkedIndex.TableName.php and throws an error since it doesn't exist.
I think what you're looking for is to be able to access the value TableName within the table LinkedIndex, correct? If so, that's accessible from within the Table model via:
$this->linkedIndex->TableName
This is made possible by the relation we set up above. $this refers to the Table model, linkedIndex refers to the LinkedIndex relation we made above, and TableName is an attribute of that LinkedIndex model.
Edit 2
Per your comments, it looks like you're trying to make a more complex relationship. I'll be honest that this isn't really the way you should be using linking tables (ideally you should have a linking table between two tables, not a linking table that says which 3rd table to link to) but I'll try and answer your question as best as possible within Yii.
Ideally, this relationship should be made from within the LinkedIndex model, since that's where the relationship lies.
Since you're using the table name as the linking factor, you'll need to create a way to dynamically pass in the table you want to use after the record is found.
You can use the LinkedIndex model's afterFind function to create the secondary link after the model is created within Yii, and instantiate the new linked model there.
Something like this for your LinkedIndex model:
class LinkedIndex extends CActiveRecord{
public $linked;
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return 'LinkedIndex';
}
public function afterFind(){
$this->linked = new Linked($this->TableName);
parent::afterFind();
}
//...etc.
}
The afterFind instantiates a new Linked model, and passes in the table name to use. That allows us to do something like this from within the Linked model:
class Linked extends CActiveRecord{
private $table_name;
public function __construct($table_name){
$this->table_name = $table_name;
}
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return $this->table_name;
}
//...etc.
}
which is how we dynamically create a class with interchangeable table names. Of course, this fails of the classes need to have separate operations done per-method, but you could check what the table_name is and act accordingly (that's pretty janky, but would work).
All of this would result in being to access a property of the linked table via (from within the Table model):
$this->linkedIndex->linked->foo;
Because the value of LinkedIndex.TableName and Table.linkedID is needed to get the values, I moved the afterFind, suggested by M Sost, directly into the Table-Class and changed its content accordingly. No more need for a virtual model.
class Table extends CActiveRecord {
public $linked; // Needs to be public, to be accessible
// ...etc.
public function afterFind() {
$model = new $this->linkedIndex->TableName;
$this->linked = $model::model()->findByPk( $this->linkedID );
parent::afterFind();
}
// ...
}
Now I get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;