Best practice to store data in the database. Laravel and Livewire - php

I'm developing an app using Laravel and Livewire, in which I have several post types, each one is store in the database using different views, because they differ in some fields, but in general are very similars. So I use one table to store them in the database.
The point is that I have one component (app/Http/Livewire/MyComponent.php & resources/views/livewire/my-component.blade.php) for each Post Type. In each one I have a method to store the data, but I know this is not the best way to do that.
I’ve been trying with Service Providers and Controllers, but not get the right way to do it.
The question is: What is the best way/practice to do this? Have a single class to store the data in the database, and call it from every where I need it.
Thanks you all.
Edit:
I have a class App\Http\Livewire\Blog to store Blog post type. Every thing works ok, but I want to put the method store() in a single class, and then call it from every Livewire's component class, because I store others post types in the same table in the database and I'm repeating the same code in every class for every component.
This is a simple example of my code:
<?php
namespace App\Http\Livewire\Blog;
use Livewire\Component;
use App\Models\BlogPosts;
use Illuminate\Support\Facades\Auth;
class Post extends Component
{
public $title, $content;
public function render()
{
return view('livewire.blog.post');
}
public function store()
{
$data = [
'title' => $this->title,
'content' => $this->content,
'author' => Auth::user()->id,
'type' => 'Blog Post',
// Other data ...
];
BlogPosts::create($data);
}
}

Related

How to save data on server without using database?

I have an application developed with Laravel. My software has settings that are used globally and should be available in all controllers (such as default information). I take this information from the database in the main controller every time a request is sent and save it in a variable.
namespace App\Http\Controllers;
class Controller extends BaseController
{
protected $config;
public function __construct()
{
$this->config= DB::table('config')->get();
}
}
Is there a way to save and use this information without the intervention of a database? I don't want to use sessions.
It is better if a solution is introduced using laravel packages.
Thanks
Assuming that you collection doesn't hold a lot of data, you can always put it inside your custom config. Create a php file inside your app/config directory, where you can put all your values like this:
<?php
return [
'key1' => value1,
'key2' => value2,
];
You can create any data structure here that you might need. Now, when you need to read single key from this data, you can use Laravel's config() helper:
$config = config('config_name.key');
If you want to get whole collection of the data, you can do it with the same config() helper, like this:
$config = config('app.config_name');
Hope that I understood your question right, and that this can lead you in right direction. You can read more about config on official documentation.

How to test laravel controller method?

My controller looks like this:
public function store(Request $request) {
$validateData = $request->validate([
'name' => 'required|unique:languages',
'code' => 'required|size:3',
'flag' => 'required|size:2'
]);
$language = new Language();
$language->name = $request->name;
$language->code = $request->code;
$language->flag = $request->flag;
$saveLanguage = $language->save();
if(!$saveLanguage){
return response()->json(['error'=>'Something went wrong, please try later.'],500);
}
return response()->json(['success'=>'Language has been created successfully', 'data'=>$language],200);
As you can see, I am instantiating a new Language object and everything works fine, but first problem is, imagine I change Language class in future (for example: you have to pass 2 parameters in constructor), I have to change this controller and every other controllers where I am instantiating Language object.
The second problem is I can't or it's too hard to test this controller.
I am curious what is the best solution to solve this problems in laravel?
For example is it a good solution to use simple factory or factory method pattern for every model I am using in my controllers.
I think when you write something like this $var = new SomeClass() in other class, this otherClass is depends on SomeClass and when you want to change SomeClass you have to update otherClass to. What do you think abaout this, how can I avoid this.
Or you could use the Eloquent Create function, this way you don't have to worry about the constructor.
$language = Language::create($request->only(['name', 'code', 'flag']));
This function will insert the data in the database and return the model.
Problem #1: If you change the signature of a constructor (e.g. adding new required parameters to the Language class constructor), then yes, you'll need to update all places where that constructor is called. There's no way around that, unless you can encapsulate the logic for what those parameters should be into a helper method somewhere (though you'll still need to update all calls the first time, while future changes will be abstracted away). However, modern IDEs (e.g. PHPStorm) can help you automate the process of replacing the old signature with the new signature.
Problem #2: You can actually test a controller like this quite easily. To take an example from the Laravel docs and apply it to your code, you could do something like this (in Laravel 7.x):
$response = $this->postJson('/language', ['name' => 'Swedish', 'code' => 'swe', 'flag => 'SE']);
$response
->assertStatus(200)
->assertJson([
'success' => 'Language has been created successfully',
])
->assertJsonPath('data.name', 'Swedish')
->assertJsonPath('data.code', 'swe')
->assertJsonPath('data.flag', 'SE');

Using two tables, where does the data retrieval go (First step in MVC)

I am new to MVC proramming (And even object based). So coming from PhP 4.* I moved into OOP, MVC and Cake..
I am building a site which Insitutes from difference COUNTRIES can use to store their data (And more). I am now building the basic per-institute registration, and would like to include a drop-down of countries.
I see two ways to approach this; Either retreive the country table information for a dropdown using the Country model:
$this->set('countries', ClassRegistry::init('Country')->getAllCountries()); (Followed by a function in \Model\Country.php)
or use the InstitutesController:
$this->set('countries', $this->Institute->Country->find('list', $params = array('fields' => array('id', 'country'))));
Which is the recommended route to take, as both seem to work?
Use the second one:
$this->set('countries', $this->Institute->Country->find('list', $params = array('fields' => array('id', 'country'))));
Both work, but, you've already got an instance of your country model (accessible via $this->Institute->Country). So, why create another instance of it? There's just no need.
There shouldn't actually be a need to specify the fields for your call to the find method. 'id' will be automatically selected as the first field, and if you set the display field of your Country model to 'country', then that will be the default uses in find('list') calls. Do it like this:
// just after class Country extends AppModel {
public $displayField = 'country';
Then, you'll just need to use this code:
$this->set('countries', $this->Institute->Country->find('list'));

how to use Lithium PHP framework with enumerated list of collections and changing collection in model

I'm looking to use Lithium framework to build my application config interface as I like its minimal approach and the document-store (i.e. Mongodb) centric model.
However, (and I know its not quite released yet), there is little-to-no information, tutorials or examples out there to move you on from the simple blog tutorial.
What I am trying to do now is build an app that will show me the collections I have in Mongodb, and then let me work with which ever collection I choose. I can't seem to figure out:
a) how would I build a model that enumerates the collections - preferably according to my internal naming scheme,
b) how do I break the convention model so I can specify the name of the collection to use?
I think there are two things i'm struggling with to answer these two questions - perhaps a fundamental misunderstanding of how to move a model in MVC beyond the simple collection-model-controller-view examples, and secondly, the actual process of telling the mongo datasource what collection to use.
any pointers or examples, gratefully received.
Chris
update::
So I figured out how to set the collection - for reference you can set source in the $_meta array like this:
protected $_meta = array(
'source' => '<<collectionName>>'
);
still no idea how to use a Model that will list me all the collections I have in my DB though. Any ideas how to do that from a philosophical and also technological manner?
further update::
so I have got a bit further thanks to the comments below. At least I might now be able to re-phrase the question a bit. I can define my model something like this:
<?php
namespace app\models;
use lithium\data\Model;
class Posts extends \lithium\data\Model{
protected $_meta = array('source' => false);
public function testcolls(){
return (self::connection()->sources());
}
}
?>
then in my view I can use:
<?php foreach ($post->testcolls() as $coll): ?>
<h2><?=$coll ?></h2>
<?php endforeach; ?>
that works - however, what I really want to do is not create a 'testcolls' method in my Model but as Medhi suggested below, I need to override the find method. I can't seem to figure out how to do that and what it would need to return. The docs are not too clear on this.
final update
based on the comment below and a bit of experimentation, I came up with the following that works for being able to call find with a collection as a parameter.
model:
class Dataqueues extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
if (isset($options['collection'])){
self::meta('source', $options['collection']);
}
return parent::find('all',$options);
}
}
controller:
class DataqueuesController extends \lithium\action\Controller {
public function index() {
$dataqueues = Dataqueues::find('all',array('limit'=>20,'collection'=>'W501'));
return compact('dataqueues');
}
}
getting a model that returns a list of collections was also pretty simple in the end:
class Collections extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
return self::connection()->sources();
}
}
note that the controller won't support options or filters.
Nothing holds you from having a Collections Model, where you set $_meta['source'] = false to prevent Lithium from looking for a Collection in your database named collections.
In this model, you can call YourModel::connection()->sources() to list all your Mongo Collections.
Docs for sources(): http://li3.me/docs/lithium/data/source/MongoDb::sources(). Basically it calls listCollections() on a MongoDB instance http://php.net/manual/en/mongodb.listcollections.php
You can override your Model::find() method to return the list of collections, instead the list of documents, or pass the collection as a param Collections::find('all', array('conditions' => ..., 'collection' => 'foo'))... or wathever you want :-)
Lithium is designed to don't force that much on you !
First of all, Lithium follows the convention over configuration approach.
What this means:
Configuration: 'source' => '<< collectionName >>'
Convention: Name your model and your collection the same thing, the framework handles the rest.
IE: A "People" collection will have a "People" model
Second, connect to your database:
Configure your connections.php file in app\bootstrap\connections.php. I know I said convention over configuration, but you still need to let the framework know where the database is and what the login info is. For details look at the http://li3.me/docs/manual/quickstart. Here is the relevant code:
// MongoDB Connection
Connections::add('default', array('type' => 'MongoDb', 'database' => 'blog', 'host' => 'localhost'));
Third, get data
Create a model, matching your collection name, and then in your controller, add this line:
$model = Models::all();
where model is the singular name for what you are storing in your collection, and Models is the name of your model. That is it.
If you put a break point after this line, you will see your Models collection. For more information, see http://li3.me/docs/manual/working-with-data/using-models.wiki
Finally, to pass it to your view, simply put this line of code at the end of your controller:
return compact('model', 'model2', 'model3');
where model would be what you just pulled in the third step. The models 2 and 3 that I tacked on is how you would pass any other collections you pulled.
In your view, you would just reference $model to and assume that the relevant fields are there. You don't have to worry about putting getters or setters or anything else like that.
For example: if you want to show the data in $model:
foreach ($model as $aModel) {
echo $aModel;
}
See Accessing View Variables in: http://li3.me/docs/manual/handling-http-requests/views.wiki
Hope this helps.

Passing two arrays from controller in cakePHP

I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);

Categories