This question already has answers here:
Reference - What does this error mean in PHP?
(38 answers)
Closed 5 years ago.
So, i'm working on building my first MVC based application.
I followed this tutorial: requiremind.com/a-most-simple-php-mvc-beginners-tutorial/ and now i'm creating a forum based on the above tutorial.
This is in my index.php
if(isset($_GET['controller']) && !isset($_GET['action'])) {
$controller = $_GET['controller'];
$action = 'topic';
} else {
$controller = 'main';
$action = 'index';
}
The first condition is for displaying pages with topic and will work for url with ?controller=PHP or something like this and set the $action as 'topic'.
Now the request will be transferred to routes.php
function call($controller, $action) {
require_once('controllers/main_controller.php');
if($controller == 'main') {
require_once('models/main_model.php');
$controller = new MainController();
} else {
require_once('models/topic_model.php');
$controller = new MainController();
}
$controller->$action();
}
$db = Db::getInstance();
$query = $db->query('SELECT cat_name FROM category');
$categories = $query->fetchAll();
foreach($categories as $category_name) {
if($controller == $category_name && $action == 'topic') {
call($controller, $action);
}
}
if(($controller == 'main') && ($action == 'index')) {
call($controller, $action);
}
Now this will make a request with database and if the topic/category exists, it will call the function.
main_controller.php
class MainController {
public function index() {
$category = Main::home();
require_once('views/home.php');
}
public function topic() {
$topics = Topic::thread_topic($_GET['controller']);
require_once('views/topic.php');
}
}
After the main_controller.php, topic_model.php:
<?php
class Topic {
public $thread_topic;
public $thread_desc;
public $thread_created_at;
public $thread_created_by;
public $category_id;
public function __construct($thread_topic, $thread_desc, $thread_created_at, $thread_created_by, $category_id) {
$this->thread_topic = $thread_topic;
$this->thread_desc = $thread_desc;
$this->thread_created_at = $thread_created_at;
$this->thread_created_by = $thread_created_by;
$this->category_id = $category_id;
}
public function thread_topic($controller) {
$list = [];
$cat_id = Fnct::cat_name_to_id($controller);
$db = Db::getInstance();
$stmt = prepare('SELECT * FROM thread WHERE thread_created_by =:cat_id');
$stmt->execute(array(':cat_id' => $cat_id));
foreach($stmt->fetchAll() as $thread) {
$list[] = new Topic($thread['thread_topic'], $thread['thread_desc'], $thread['thread_created_at'], $thread['thread_created_by'], $thread['category_id']);
}
return $list[];
}
}
?>
And now the views/topic.php
<table border='1'>
<tr>
<th>Thread Topic</th>
<th>Thread Desc</th>
<th>Thread Started At</th>
</tr>
<tr>
<?php foreach($topics as $topic) { ?>
<td><?php echo $topic->thread_topic; ?></td>
<td><?php echo $topic->thread_desc; ?></td>
<td><?php echo $topic->thread_created_at; ?></td>
<?php } ?>
</tr>
</table>
The problem is this, it is displaying blank page. And giving no error.
And i'm still struggling to figure out what is wrong here. Please tell me what i did wrong here??
Edit: In routes.php moves $controller = new MainController() under if statement. But still same blank page is displaying.
Well, your error is in your routes.php as mentioned above. I'll try to explain it here in an answer and also directly fix the mistakes you did.
Okay, lets start. First of all, lets ignore the call() function first and only look at the other part:
Your Code:
$db = Db::getInstance();
$query = $db->query('SELECT cat_name FROM category');
$categories = $query->fetchAll();
foreach($categories as $category_name) {
if($controller == $category_name && $action == 'topic') {
call($controller, $action);
}
}
if(($controller == 'main') && ($action == 'index')) {
call($controller, $action);
}
You should know that fetchAll() will always return an arrqay, not a string, thats why you can't compare the result directly with a string. Change the code to:
$db = Db::getInstance();
$query = $db->query('SELECT cat_name FROM category');
$categories = $query->fetchAll();
foreach($categories as $category_name) {
if($controller == $category_name["cat_name"] && $action == 'topic') {
call($controller, $action);
}
}
if(($controller == 'main') && ($action == 'index')) {
call($controller, $action);
}
You see the $categoriy_name["cat_name"] - You have to give the key, since $category_name is an array, not a string. (May look at some tutorials for PHP and SQL, like how all the fetch method works and what their output is).
And then the second part, your call function
Your code:
function call($controller, $action) {
require_once('controllers/main_controller.php');
$controller = new MainController();
if($controller == 'main') {
require_once('models/main_model.php');
} else {
require_once('models/topic_model.php');
}
$controller->$action();
}
So, you pass $controller as an argument to that function. Thats correct. But then you assign $controller = new MainController(); so $controller is no longer the string you passed to the function, but now ins an object of the class MainController. So you loose your value on what you passed to the function. And then only 1 Line after you do:
if($controller == 'main') {
This will never be true. At this point $controller is an object of your mainController() and the argument will always evaluate to false, so you'll always load the topic_model.php Since your topic() function is inside the MainController() its okay, but its not what MVC is for. Thats just.. some random code there. Thats not MVC, nor good practice.
But well, I don't know what you want to do there with your main controller, I'm not going to dive more into that topic. With the changes of your foreach-loop the call should work and your view should be loaded.
You don't want something like a MainController that is always laoded, or at least not for those methods you have inside - May look up some tutorials on MVC. You only wanna load the controller for the specific section you're in.
Anyway, I wish you all the best for the future and happy coding! :)
Related
I am trying to cache a sitemap generated from a controller for a website that I am working on, but apparently I am doing something wrong, as I don't understand the Error message.
Here is the code snippet causing the trouble (it is a controller Method). Everything works correctly until I add the caching.
public function mapContent($type, Request $request)
{
$cachingKey = $request->fullUrl();
if ($type !== 'news' && $type !== 'pages') {
abort(404);
} elseif (Cache::has($cachingKey)) {
$items = Cache::get($cachingKey);
} else {
$items = $this->_getElementsSitemap($type);
Cache::put($cachingKey, $items, now()->addDays(7));
}
return Response::make($items, '200')->header('Content-Type', 'application/xml');
}
Seems that $items = $this->_getElementsSitemap($type); returns not serializable instance.
Your class should implement __serialize method
I want to implement a Sugar Logic Hook that fires when invoice status change to 'Validated'.
This my logic hook :
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['after_save'] = Array();
$hook_array['after_save'][] = Array(1, 'status invoices Changes', '/var/www/html/suitecrm/modules/AOS_Invoices/AOS_LogicHooks.php','AOS_LogicHooks', 'statusInvoicesChanges');
?>
This is my action class:
<?php class AOS_LogicHooks {
public function statusInvoicesChanges (SugarBean $bean, $event, $arguments){
if($bean->status == 'Validated' && $bean->fetched_row->status <> $bean->status){
$GLOBALS['log']->fatal("Status has been changed");
}}}
?>
What do I missing?
Simple mistake we all do, fetched row is an Array so you cannot access the content as a property.
I just tested this and works fine! take a look:
<?php
class AOS_LogicHooks
{
public function statusInvoicesChanges(SugarBean $bean, $event, $arguments)
{
if ($bean->status == 'Validated' && !empty($bean->fetched_row) && $bean->fetched_row['status'] <> $bean->status) {
$GLOBALS['log']->fatal("Status has been changed");
}
}
}
I'm trying to follow the instructions on this docs page, but I seem to be missing something:
https://docs.joomla.org/Supporting_SEF_URLs_in_your_component
The URI that needs to be corrected is: index.php?com_component&view=legal&page=customermasteragreement
It seems like the routing function should be simple, but the page is just displaying default instead of the sub-view.
Here's my current code:
function ComponentBuildRoute(&$query)
{
$segments = array();
if (isset($query['view'])) {
$segments[] = $query['view'];
unset($query['view']);
}
if (isset($query['page'])) {
$segments[] = $query['page'];
unset($query['page']);
}
return $segments;
}
function ComponentParseRoute($segments)
{
$vars = array();
switch($segments[0])
{
case 'legal':
$vars['view'] = 'legal';
break;
case 'customermasteragreement':
$vars['page'] = 'customermasteragreement';
break;
}
return $vars;
}
Update
This code works to display the subpage, but it gives me a URI like: legal-agreements/legal?page=customermasteragreement
class ComponentRouter extends JComponentRouterBase {
public function build(&$query) {
$segments = array();
$view = null;
if (isset($query['view'])) {
$segments[] = $query['view'];
$view = $query['view'];
unset($query['view']);
}
if (isset($query['id'])) {
if ($view !== null) {
$segments[] = $query['id'];
} else {
$segments[] = $query['id'];
}
unset($query['id']);
}
return $segments;
}
public function parse(&$segments) {
$vars = array();
// View is always the first element of the array
$vars['view'] = array_shift($segments);
return $vars;
}
}
EDIT 2
If it helps, here's my model and views
models/legal.php
// import Joomla modelitem library
jimport('joomla.application.component.modelitem');
class ComponentModelLegal extends JModelItem {
public function __construct($config = array())
{
JLoader::register('ComponentHelper', JPATH_COMPONENT_ADMINISTRATOR . '/helpers/component.php');
parent::__construct($config);
}
/**
*
* #return string
*/
public function getLegal() {
$app = JFactory::getApplication();
$page = $app->input->get('page', '', 'STRING');
if ($page) {
ComponentHelper::add('type', $page); //This is an API request to an external service, returning JSON formatted data
$legal = ComponentHelper::getData('commons/legal-agreements.json', TRUE);
if (isset($legal[0]['status'])) {
JError::raiseError(400, $legal[0]['ERROR']);
return false;
} else {
if (!isset($this->legal)) {
$this->legal = $legal;
}
return $this->legal;
}
}
}
}
views/legal/view.html.php
class ComponentViewLegal extends JViewLegacy {
function display($tpl = null) {
// Assign data to the view
$this->legal = $this->get('legal');
// Check for errors.
if (count($errors = $this->get('Errors'))) {
JLog::add(implode('<br />', $errors), JLog::WARNING, 'jerror');
return false;
}
// Display the view
parent::display($tpl);
}
}
views/legal/tmpl/default.php
$page = JRequest::getVar('page');
$pages = array(
'resellermasteragreement',
'customermasteragreement',
'resellerdomainagreement',
'customerdomainagreement',
'resellerwebserviceagreement',
'customerwebserviceagreement',
'resellerdigicertagreement',
'customerdigicertagreement',
'registraragreement',
'customerhostingproductagreement',
'resellerhostingproductagreement'
);
?>
<div class="col-lg-12">
<?php
echo in_array($page, $pages) ? $this->loadTemplate('legal') : $this->loadTemplate('home');
?>
</div>
views/legal/tmpl/default_legal.php
$page = JRequest::getVar('page');
echo nl2br(htmlspecialchars($this->legal[$page]['defaultagreement'], ENT_NOQUOTES, "UTF-8"));
?>
<a type="button" class="btn btn-primary" href="<?php echo JROUTE::_("index.php?option=com_component&view=legal"); ?>">Back</a>
EDIT 3
This works because I wasn't navigating directly to the page from a menu item. There's a top level menu, then a page a links to the agreements. I have a hidden menu to each item, but that wasn't the link I followed.
$app = JFactory::getApplication()->getMenu();
$customermaster = $app->getItems( 'link', 'index.php?option=com_component&view=legal&page=customermasteragreement', true );
JRoute::_('index.php?Itemid='.$customermaster->id);
You have one view but you're not actually set the page argument correctly when you catch a view argument, but you treat page as some king of another view. Can you please try this and check if it works:
function [componentname]ParseRoute($segments)
{
$vars = array();
switch($segments[0])
{
case 'legal':
$vars['view'] = 'legal';
$vars['page'] = $segments[1];
break;
}
return $vars;
}
Also the functions ComponentBuildRoute, ComponentParseRoute must have your components name instead of Component at the beginning.
EDIT
[componentname]BuildRoute and [componentname]ParseRoute are deprecated, you should stick the the class that extends JComponentRouterBase as you have it in your updated second example. Try this one here and see if it works:
class ComponentRouter extends JComponentRouterBase {
public function build(&$query) {
$segments = array();
$view = null;
if (isset($query['view'])) {
$segments[] = $query['view'];
$view = $query['view'];
unset($query['view']);
}
if (isset($query['page'])) {
$segments[] = $query['page'];
unset($query['page']);
}
return $segments;
}
public function parse(&$segments) {
$vars = array();
// View is always the first element of the array
$vars['view'] = array_shift($segments);
if(count($segments) > 0)
$vars['page'] = array_shift($segments);
return $vars;
}
}
EDIT 2
I have a test Joomla 3 site with a simple component named Ola.
Non SEO component URL: http://j3.dev.lytrax.net/index.php?option=com_ola&page=test_page&view=ola
URL generated by JRoute before Router class added to router.php: http://j3.dev.lytrax.net/index.php/component/ola/?page=test_page&view=ola
SEO URL returned by JRoute::_('index.php?option=com_ola&page=test_page&view=ola'); after creating router.php and used the Router class above: http://j3.dev.lytrax.net/index.php/component/ola/ola/test_page
If you visit the links, you'll see that all are working and you can even see the page, view and JRoute results of the calling component Ola.
Unless I've completely misunderstood Joomla (I've been developing with it for four five years now), there is no "sub-view" concept implemented. Trying to use the idea is what's getting you into trouble I think.
I'm astounded that the idea of using a visual debugger is so unpopular.
Get yourself something like Netbeans (there are others too), set up a local web and database server, and watch the code run. Very (well, relatively very) quickly you'll start to understand how the structure hangs together.
You may put code in your view that picks up the extra params you've set and displays or hides things accordingly, but there is no "sub-view".
I do not know how to set a callback function for the view record page in codeigniter.
I use the callback_column function and it does what I need in the grid view, but on the view record page it does not work.
I searched their site and forum and did not found anything that could help me.
My code looks like:
$zeus = new grocery_CRUD();
$zeus->set_theme('bootstrap');
// $zeus->set_language('romanian');
$zeus->set_table('programari');
$zeus->columns(array('id_client', 'id_sala', 'denumire', 'numar_persoane', 'observatii'));
$zeus->callback_column('id_sala',array($this,'_test_function'));
$cod = $zeus->render();
$this->_afiseaza_panou($cod);
public function _test_function($row, $value)
{
return '0';
}
write this lines in \libraries\Grocery_CRUD.php
at line number 3530
protected $callback_read_field = array();
than put this function after constructor call
public function callback_read_field($field, $callback = null)
{
$this->callback_read_field[$field] = $callback;
return $this;
}
//Now update this function to manage the field outputs using callbacks if they are defined for the same
protected function get_read_input_fields($field_values = null)
{
$read_fields = $this->get_read_fields();
$this->field_types = null;
$this->required_fields = null;
$read_inputs = array();
foreach ($read_fields as $field) {
if (!empty($this->change_field_type)
&& isset($this->change_field_type[$field->field_name])
&& $this->change_field_type[$field->field_name]->type == 'hidden') {
continue;
}
$this->field_type($field->field_name, 'readonly');
}
$fields = $this->get_read_fields();
$types = $this->get_field_types();
$input_fields = array();
foreach($fields as $field_num => $field)
{
$field_info = $types[$field->field_name];
if(isset($field_info->db_type) && ($field_info->db_type == 'tinyint' || ($field_info->db_type == 'int' && $field_info->db_max_length == 1))) {
$field_value = $this->get_true_false_readonly_input($field_info, $field_values->{$field->field_name});
} else {
$field_value = !empty($field_values) && isset($field_values->{$field->field_name}) ? $field_values->{$field->field_name} : null;
}
if(!isset($this->callback_read_field[$field->field_name]))
{
$field_input = $this->get_field_input($field_info, $field_value);
}
else
{
$primary_key = $this->getStateInfo()->primary_key;
$field_input = $field_info;
$field_input->input = call_user_func($this->callback_read_field[$field->field_name], $field_value, $primary_key, $field_info, $field_values);
}
switch ($field_info->crud_type) {
case 'invisible':
unset($this->read_fields[$field_num]);
unset($fields[$field_num]);
continue;
break;
case 'hidden':
$this->read_hidden_fields[] = $field_input;
unset($this->read_fields[$field_num]);
unset($fields[$field_num]);
continue;
break;
}
$input_fields[$field->field_name] = $field_input;
}
return $input_fields;
}
than call same as other callback functions
As far as I'm aware GroceryCRUD doesn't provide callbacks or another means of overriding the default output in the view state.
The solution to customising this would be to create a custom view to which you will insert the data from your record. This way you can customise the layout and other presentation.
What you would then do is unset the default read view with:
$crud->unset_read();
And add a new action where there are details on how to do this here.
What to do with the new action is point it to a URL that you map in routes.php if necessary and handle it with a new function in your controller. You'll either have to write a model function to retrieve the data since this isn't passed from GC or you can use the action to target a callback and feed $row to it via POST or something so that the data for the record is accessible in the view. (Look at the example in the link above).
Let's say I have a class...
class A {
private $action;
private $includes;
public function __construct($action, $file) {
//assign fields
}
public function includeFile()
include_once($this->file);
}
$a = new A('foo.process.php', 'somefile.php');
$a->includeFile();
As you can see, includeFile() calls the include from within the function, therefore once the external file is included, it should technically be inside of the function from my understanding.
After I've done that, let's look at the file included, which is somefile.php, which calls the field like so.
<form action=<?=$this->action;?> method="post" name="someForm">
<!--moar markup here-->
</form>
When I try to do this, I receive an error. Yet, in a CMS like Joomla I see this accomplished all the time. How is this possible?
Update
Here's the error I get.
Fatal error: Using $this when not in object context in /var/www/form/form.process.php on line 8
Update 2
Here's my code:
class EditForm implements ISave{
private $formName;
private $adData;
private $photoData;
private $urlData;
private $includes;
public function __construct(AdData $adData, PhotoData $photoData, UrlData $urlData, $includes) {
$this->formName = 'pageOne';
$this->adData = $adData;
$this->photoData = $photoData;
$this->urlData = $urlData;
$this->includes = $includes;
}
public function saveData() {
$this->adData->saveData();
$this->photoData->saveData();
}
public function includeFiles() {
if (is_array($this->includes)) {
foreach($this->includes as $file) {
include_once($file);
}
} else {
include_once($this->includes);
}
}
public function populateCategories($parent) {
$categories = $this->getCategories($parent);
$this->printCategories($categories);
}
public function populateCountries() {
$countries = $this->getCountries();
$this->printCountries($countries);
}
public function populateSubCategories() {
//TODO
}
private function getCategories($parent) {
$db = patentionConnect();
$query =
"SELECT * FROM `jos_adsmanager_categories`
WHERE `parent` = :parent";
$result = $db->fetchAll(
$query,
array(
new PQO(':parent', $parent)
)
);
return $result;
}
private function getCountries() {
$db = patentionConnect();
$query =
"SELECT `fieldtitle` FROM `jos_adsmanager_field_values`
WHERE fieldid = :id";
$result = $db->fetchAll(
$query,
array(
new PQO(':id', 29)
)
);
return $result;
}
private function printCountries(array $countries) {
foreach($countries as $row) {
?>
<option value=<?=$row['fieldtitle'];?> >
<?=$row['fieldtitle'];?>
</option>
<?php
}
}
private function printCategories(array $categories) {
foreach($categories as $key => $row){
?>
<option value=<?=$row['id'];?>>
<?=$row['name'];?>
</option>
<?php
}
}
}
And the include call (which exists in the same file):
$template = new EditForm(
new AdData(),
new PhotoData(),
new UrlData($Itemid),
array(
'form.php',
'form.process.php'
)
);
$template->includeFiles();
And the main file which is included...
if ($this->formName == "pageOne") {
$this->adData->addData('category', $_POST['category']);
$this->adData->addData('subcategory', $_POST['subcategory']);
} else if ($this->formName == "pageTwo") {
$this->adData->addData('ad_Website', $_POST['ad_Website']);
$this->adData->addData('ad_Patent', $_POST['ad_Patent']);
$this->adData->addData('ad_Address', $_POST['ad_Address']);
$this->adData->addData('email', $_POST['email']);
$this->adData->addData('hide_email', $_POST['hide_email']);
$this->adData->addData('ad_phone', $_POST['ad_phone']);
$this->adData->addData('ad_Protection', $_POST['ad_Protection']);
$this->adData->addData('ad_Number', $_POST['ad_Number']);
$this->adData->addData('ad_Country', $_POST['ad_Country']);
$this->adData->addData('ad_issuedate', $_POST['issuedate'] . '/' . $_POST['issuemonth'] . '/' . $_POST['issueyear']);
} else if ($this->formName == "pageThree") {
$this->adData->addData('name', $_POST['name']);
$this->adData->addData('ad_Background', $_POST['ad_Background']);
$this->adData->addData('ad_opeartion', $_POST['ad_operation']);
$this->adData->addData('ad_advlimit', $_POST['ad_advlimit']);
$this->adData->addData('ad_status', $_POST['ad_status']);
$this->adData->addData('ad_addinfo', $_POST['ad_addinfo']);
$this->adData->addData('ad_description', $_POST['ad_description']);
$this->adData->addData('tags', $_POST['tags']);
$this->adData->addData('videolink', $_POST['videolink']);
} else if ($this->formName == "pageFour") {
foreach($_POST['jos_photos'] as $photo) {
$this->photoData->addData(
array(
'title' => $photo['title'],
'url' => $photo['url'],
'ad_id' => $this->photoData->__get('ad_id'),
'userid' => $this->photoData->__get('userid')
)
);
}
}
Update
Strange: while the error itself hadn't been quite related to what the problem was, I found that it was simply an undefined field which I hadn't implemented.
Consider this thread solved. To those who replied, I certainly appreciate it regardless.
This should work. Are you sure you're doing the include from a non-static method that's part of the class (class A in your example)? Can you post the exact code you're using?
Edit: As general advice for problems like this, see if you can trim down the code so the problem is reproducible in a short, simple example that anyone could easily copy/paste to reproduce the exact error. The majority of the time, you'll figure out the answer yourself in the process of trying to simplify. And if you don't, it will make it much easier for others to help you debug.