Sonata Admin bundle Doing a One to Many to One join results in a null pointer in the first One-Many join - sonata-admin

I have a One To Many To One relationship between Page, PageMedia and Sonata Media Bundles Media class.
I get the insert into the PageMedia and Page but for some reason the page id in the page media table always comes up null.
Here are the relevent parts of my Objects I am trying to work with.
class Page {
/** #ORM\OneToMany(targetEntity="PageMedia", mappedBy="page", cascade={"persist"} */
protected $pageMedias;
}
class PageMedia {
/** #ORM\ManyToOne(targetEntity="Page", cascade={"all"}, inversedBy="pageMedias") ) */
protected $page;
/** #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", cascade={"persist"}) */
protected $media;
}
class PageAdmin extends Admin {
protected function configureFormFields(FormMapper $form) {
$form
->with('Media')
->add('pageMedias', 'sonata_type_collection',
array('by_reference' => false ), array(
'edit'=>'inline',
'inline' => 'table',
'sortable' => 'sortOrder'
))
->end();
}
}
class PageMediaAdmin extends Admin {
protected function configureFormFields(FormMapper $form) {
$form->add('media', 'sonata_type_model_list',
array(),
array('link_parameters'=>array('context' =>'default')));
}
}
Maybe someone can help me solve the problem? I am at an utter loss here... Even after adding a fale nullable to the joinColumn between page and page media now I cannot insert, what did I miss?
Thanks again.

In your Page class, you should have a "addPageMedia" method...like this:
$this->pageMedias[] = $pageMedia;
you have to add this before
$pageMedia->setPage($this)
or something similar! :)

Related

How to get this code work to store in Laravel. No errors and no storage

I can't store name and IP address to DB. I created a table 'info' with appropriate fields by running php artisan migrate.
A schema
Schema::create('info', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('ip');
$table->timestamp('created_at')->nullable();
});
A model for Info
class Info extends Model
{
protected $fillable = ['ip', 'name'];
}
Maybe the problem is in my HomeController where I get those variables?
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Info;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request) {
Info::create(['info' => $request->input('info')]);
}
public function index()
{
if (Auth::check())
{
$name = Auth::user()->name;
$ip = Request::ip();
\App\Events\eventIp::dispatch($ip,$name);
return view('home');
}
}
}
My routes in web.php
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
});
But it doesn't work. Gives no errors and no records in DB.
Something make me think that it have to do with my index function. I got info in function index and maybe function store doesn't have a clue what I mean.
A controller action is basically a method that usually gets executed when you open an url (as you connect them to routes).
In your example you have connected two routes to their respective actions:
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
Now, when you log in succesfully, imagine that you end up in the page with url http://localhost:8000/home in your web browser.
The key difference is the method which you use to call your route (you can get an overview of the differences here), in your case you are using GET method.
The resulting action executed it the one associated to /home route with the GET method, that is the HomeController#index action (or method).
The store method, although is in the same HomeController class, doesn't get triggered unless you execute the /home route, but with the POST method.
You can confirm that if you put a debug message in each of the methods like this:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request)
{
echo 'I will not be executed';
}
public function index()
{
echo 'I have been executed';
}
}
If you want to simply save a info record when you visit the /home route with the GET method, you can put the save in the index method itself and get rid of the store method:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
// You can omit Auth::check() because you are using the auth middleware
// that exactly does this job.
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
return view('home');
}
}
Keep in mind that doing in this way you will get a new database record for each page view you make to that route (if you keep refreshing the page, you should see new records being added to database).
Update
When you use Eloquent Models, laravel will look for a table named after the pluralized model name (Info model will try to use infos table).
However you created a table named info. To solve that you can either rename the table and rerun the migration with php artisan migrate:refresh (it will delete all the existing data in the database you are using for your laravel app)
Or specify the table name to use for that laravel model:
class Info extends Model
{
protected $table = 'info';
protected $fillable = ['ip', 'name'];
}
How are you calling the functions? There is a couple of things wrong with your code, but you're saying there are no errors at all.
Firstly, your Info::create call does not need the ['info' => $request->input('info')] info. This is because your Info model has no database property called info, but normally you would get an obvious error with the approach, which is why I expect you are also calling the store method incorrectly.
Call the create method like so:
$infoModel = Info::create(['name' => $request->input('name'), 'ip' => $request->input['ip']]);
or, if you can guarantee your $request only contains the needed fields (properly validated), you can just do
$infoModel = Info::create($request->all());
Add a little more info to the question on how you are calling store and we can probably solve the rest of your problem.
Within your store function inside HomeController , use
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
and make sure Info model is imported.
Also make sure your route has the call to store function while POSTing data .

How to extend entity classes with custom functions (business logic) in cakephp v3

In cakephp 3 (3.3.5, that is) I want to extend my entity classes with custom functions (business logic). For example:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Something extends Entity {
public function isFoo() {
return true;
}
}
The corresponding table object looks like this:
namespace App\Model\Table;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use App\Model\Entity\Something; // produces an `unused import' warning
class SomethingsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
...
}
...
}
In the controller, I use this code to retrieve the entity from the database and call the custom function:
class SomeOtherController extends AppController {
...
$this->loadModel('Somethings');
$thing = $this->SomethingsTable->get($id);
if ($thing->isFoo()) { ... }
...
}
However, this fails with a fatal error:
Error: Call to undefined method Cake\ORM\Entity::isFoo()
Note, when I do a
<?= var_dump($thing,true); ?>
in the corresponding view, $thing is shown as of type Cake\ORM\Entity.
How can I change the table's get() function to return entities with the correct type "Something" ?
It should be:
$thing = $this->Somethings->get($id);
// not
$thing = $this->SomethingsTable->get($id);
Thats why the Something entity is not used, but the default Entity class.
CakePHP autotables, since it can not find the SomethingsTableTable the default table class is used. Therefore also the default entity class is loaded.
If your test method would contain a query to the db, there would have been an error thrown, saying that somethings_table does not exist.
The problem is probably here:
class SomeOtherController extends AppController {
$this->loadModel('Somethings');
$thing = $this->SomethingsTable->get($id); // <-- Here
if ($thing->isFoo()) { ... }
}
Controller::loadModel does not set $this->SomethingsTable (which was probably set somewhere else in your code... ), but $this->Somethings, so this should be:
$this->loadModel('Somethings');
$thing = $this->Somethings->get($id);
if ($thing->isFoo()) { }
This code works, you do not need use App\Model\Entity\Something in SomethingsTable.php.
When trying to debug such thing, use debug() instead of var_dump:
Configure::write('debug', true); // If you are not already in debug mode
$this->loadModel('Somethings');
debug($this->Somethings);
Output:
object(App\Model\Table\SomethingsTable) {
'registryAlias' => 'Somethings',
'table' => 'somethings',
'alias' => 'Somethings',
'entityClass' => 'App\Model\Entity\Something', // Good!
'associations' => [],
'behaviors' => [],
'defaultConnection' => 'default',
'connectionName' => 'default'
}
This is an old post but I faced this issue today and the solution for me was slightly different. I was loading the model the right way, but my class name was not following naming conventions.
My Table: JobProfitsTable.php
My Entity: JobProfits.php (plural)
CakePhp is automatically looking for class named JobProfit.php (singular), and seems to fallback on Cake\ORM\Entity
So I had 2 options:
Rename my entity into JobProfit.php
Update my Table class with $this->setEntityClass('JobProfits')

Add a subdomain to all urls in Cakephp

I want all Cakephp urls to use a subdomain if it is present in the users session... so if there is an entry 'subdomain:user' in the session of the user all pages will have 'user' as a prefix: Eg user.example.com, user.example.com/settings.
Is there an easy way to do this?
thanks,
kSeudo
There are custom route classes you could use. Create a file in APP/Lib/Route called UserSubdomainRoute.php and put this in it.
<?php
App::uses('AuthComponent', 'Controller/Component');
class UserSubdomainRoute extends CakeRoute {
/**
* #var CakeRequest
*/
private $Request;
/**
* Length of domain's TLD
*
* #var int
*/
public static $_tldLength = 1;
public function __construct($template, $defaults = array(), $options = array()) {
parent::__construct($template, $defaults, $options);
$this->Request = new CakeRequest();
$this->options = array_merge(array('protocol' => 'http'), $options);
}
/**
* Sets tld length
*
* #param $length
* #return mixed void|int
*/
public static function tldLength($length = null) {
if (is_null($length)) {
return self::$_tldLength;
}
self::$_tldLength = $length;
}
/**
* Writes out full url with protocol and subdomain
*
* #param $params
* #return string
*/
protected function _writeUrl($params) {
$protocol = $this->options['protocol'];
$subdomain = AuthComponent::user('subdomain');
if (empty($subdomain)) {
$subdomain = 'www';
}
$domain = $this->_getDomain();
$url = parent::_writeUrl($params);
return "{$protocol}://{$subdomain}.{$domain}{$url}";
}
/**
* Get domain name
*
* #return string
*/
protected function _getDomain() {
return $this->Request->domain(self::$_tldLength);
}
}
One improvement to the class would probably be to make the $Request static.
Unfortunately in Cake 2.0 there is no way to set a defaultRotueClass, however, I added that feature in 2.1+ and I don't want to tell you to upgrade so you are going to have to manually specify it for all your routes in the third param like so:
Router::connect(..., ..., array('routeClass' => 'UserSubdomainRoute');
Be sure to add at the top of routes.php
App::uses('UserSubdomainRoute', 'Lib/Route');
If you do upgrade to 2.1+ you can just add this at the top of your routes.php
Router::defaultRouteClass('UserSubdomainRoute');
Then any routs specified after will use that route class.
The main part of the route class is the _writeUrl method. It checks to see if there is a subdomain key set in the session otherwise uses www and builds the full url to return.
Heads Up: Haven't tested the class, still at school just wanted to give you a jump start. It's just a modifed version of my SubdomainRoute which works a bit differently (it used to only match routes to when a certain subdomain is set, for ex in my app matches clients subdomain to my ClientsAdminPanel plugin. You can grab that here: http://bin.cakephp.org/view/50699397 So you can see how that's done as well if you need a combination of UserSubdomainRoute and my SubdomainRoute (in the link).
Hope this helps for now. Let me know if there are any problems.
Edit:
Here's how to force a redirection - something tells me there's a better way. I'll update if I can think of it.
public function beforeFilter() {
$subdomains = $this->request->subdomains();
$subdomain = $this->Auth->user('subdomain');
if (!empty($subdomain) && !in_array($subdomain, $subdomains)) {
$this->redirect('http://' . $subdomain . '.site.com' . $this->request->here);
}
}
well if you wish to do it for all your links probably you use the same way to show all your url on your site, do you use something like $this->html->link('/blah/asd/','blah'); ?
IN Cake/Routing/Route
class SubdomainRoute extends CakeRoute {
public function match($params) {
$subdomain = isset($params['subdomain']) ? $params['subdomain'] : null;
unset($params['subdomain']);
$path = parent::match($params);
if ($subdomain) {
$path = 'http://' . $subdomain . '.localhost' . $path;
}
return $path;
}
}
When creating links you could do the following to make links pointing at other subdomains.
echo $this->Html->link(
'Other domain',
array('subdomain' => 'test', 'controller' => 'posts', 'action' => 'add')
);

CakePHP 2.1 HTTP cache

I'm trying to speed up my site by taking advantage of the new HTTP cache features in CakePHP 2.1:
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
$this->response->modified($article['Article']['modified']);
$this->set(compact('article'));
}
}
Caching works fine, but does not distinguish between different users (i.e. if a user logs in and visits a page that was already cached, the previously cached page is displayed, and user-specific content is not shown). I'd like one of the following to happen:
Cache discriminates between different users and stores a separate cache for each user
Caching is disabled if a user is logged in (the user login is only used for admin purposes)
I've tried adding
if (AuthComponent::user('id')) {
$this->disableCache();
}
But this doesn't seem to solve the problem
Does anyone know how to get this to work, or am I doing something fundamentally wrong?
You could try the etag caching method and generate a hash based on the article id and user id.
See http://book.cakephp.org/2.0/en/controllers/request-response.html#the-etag-header
The Etag header (called entity tag) is string that uniquely identifies the requested resource. It is very much like the checksum of a file, caching will compare checksums to tell whether they match or not.
To actually get advantage of using this header you have to either call manually CakeResponse::checkNotModified() method or have the RequestHandlerComponent included in your controller:
<?php
public function index() {
$articles = $this->Article->find('all');
$this->response->etag($this->Article->generateHash($articles));
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
...
}
I thought I'd post the solution(s) I eventually used, in case it helps anyone.
To disable caching completely for logged in users:
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
if (!AuthComponent::user('id')) {
$this->response->etag($this->Article->generateHash($article));
}
$this->set(compact('article'));
}
}
To have a separate cache for each user (and for the case when no-one is logged in):
class Article extends AppModel {
public function generateHash($article) {
if (AuthComponent::user('id')) {
return md5(AuthComponent::user('id') . '-' . $article['Article']['modified']);
} else {
return md5($article['Article']['modified']);
}
}
}
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
$this->response->etag($this->Article->generateHash($article));
$this->set(compact('article'));
}
}

Cakephp - how to make error pages have its own layouts?

I wanna have a different layout for the page not found 404 page. How can i set a different layout for that page?
Savant from the IRC helped me out and he suggest in using beforeRender(){} in the app_controller
// Before Render
function beforeRender() {
if($this->name == 'CakeError') {
//$this->layout = 'error';
}
}
CakeError is a catchAll for errors :D
In CakePHP 2.2.2 I changed the ExceptionRenderer in core.php with my own, like this:
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer', // this is ExceptionRenderer by default
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer {
protected function _outputMessage($template) {
$this->controller->layout = 'error';
parent::_outputMessage($template);
}
}
Just you need to make layout changes in your error400.ctp file under /app/View/Errors/error400.ctp
Open that file and set layout by
<?php $this->layout=''; //set your layout here ?>
better to create an error.php file in your app folder
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = 'error';
parent::error404($params);
}
}
so you can avoid the if-testing at EVERY page render that savants' solution introduces
My solution for CakePHP 2.3
Change the ExceptionRenderer in core.php to use your own renderer.
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer',
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer
{
/**
* Overrided, to always use a bare controller.
*
* #param Exception $exception The exception to get a controller for.
* #return Controller
*/
protected function _getController($exception) {
if (!$request = Router::getRequest(true)) {
$request = new CakeRequest();
}
$response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
$controller = new Controller($request, $response);
$controller->viewPath = 'Errors';
$controller->layout = 'error';
return $controller;
}
}
The advantage to this approach is that it ensures any exceptions thrown from AppController don't cause an endless loop when rendering the exception. Forces a basic rendering of the exception message every time.
This simplest way I know of is to create this function in your AppController:
function appError($method, $messages)
{
}
You can then do whatever you want with the error, display it however you like, or not display it at all, send an email etc.. (I'm not sure if this method if still valid.)
There is also an option of creating app_error.php in your app root, with class AppError extends ErrorHandler in it, which enables you to override all kinds of errors. But I haven't done this yet, so I can't tell you more about it.
See cake/libs/error.php and cake/libs/object.php and of course The Book for more info.
Edit: Forgot to mention, once you caught the error, there's nothing preventing you to - for example - store the error in session, redirect to your "error handling controller", and then display it in your controller however you want.

Resources