skip to Main Content

Thank you very much for your help.

I am using Yii2. I want to use a REST POST request to send a list of words to the server, the server will translate the words and send back their translation.

When I send the POST request, I get this message in the browser JavaScript console:
name "Bad Request"
message "Missing required parameters: words"
code 0
status 400
type "yiiwebBadRequestHttpException"

I am running PHP using this command: php yii serve.

JavaScript

window.onload = (event) => {
    document.querySelector("#translate").addEventListener("click", translate)
}

function translate() {
    const textarea = document.getElementById("words");
    const words = textarea.value.replace(/rn/g, "n").split("n");
    post('http://localhost:8080/index.php/rest-translation/translate', words)
}

async function post(url, words) {
    console.log(words)

    let options = {
        method: "POST",
        body: JSON.stringify({words: words})
    }

    const response = await fetch(url, options)
    if (response.ok) {
        response.json()
    }
}

This is the view:

<?php

use yiihelpersHtml;
use yiiwidgetsDetailView;
use yiiwidgetsActiveForm;
use appassetsTranslationAsset;

/** @var yiiwebView $this */
/** @var appmodelsBook $book */
/** @var appmodelsBookSection $section */

$this->title = $section->title;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Section'), 'url' => ['book']];
$this->params['breadcrumbs'][] = $this->title;
yiiwebYiiAsset::register($this);
?>
<div class="translate-section">

    <h1><?= Html::encode($this->title) ?></h1>

    <?= DetailView::widget([
        'model' => $book,
        'attributes' => [
            'id',
            'name',
        ],
    ]) ?>
    <?= DetailView::widget([
        'model' => $section,
        'attributes' => [
            'title',
        ],
    ]) ?>
    <div>
        <?php $form = ActiveForm::begin(); ?>
        <table>
            <tr>
                <td><textarea id="words"></textarea></td>
                <td><textarea id="first-translation"></textarea></td>
                <td><textarea id="second-translation"></textarea></td>
            </tr>
        </table>
        <button id="translate" type="button">Translate</button>

        <div class="form-group">
            <?= Html::submitButton(Yii::t('app', 'Save'), ['class' => 'btn btn-success']) ?>
        </div>

        <?php ActiveForm::end(); ?>

    </div>
    <?php TranslationAsset::register($this) ?>
</div>

This is the REST controller:

<?php

namespace appcontrollers;

use appmodelsTranslation;
use yiirestActiveController;

class RestTranslationController extends ActiveController
{
    public $modelClass = 'appmodelsTranslation';

    public function actionTranslate($words)
    {
        return  Translation::translate($words);
    }

    public function behaviors()
    {
        $behaviors = parent::behaviors();

        // remove authentication filter
        $auth = $behaviors['authenticator'];
        unset($behaviors['authenticator']);

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => yiifiltersCors::class,
        ];

        // re-add authentication filter
        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        $behaviors['authenticator']['except'] = ['options'];

        return $behaviors;
    }
}

This is the model:

<?php

namespace appmodels;

use Yii;

/**
 * This is the model class for table "translation".
 *
 * @property int $id
 * @property int $sourceEntryId
 * @property string $languageId
 * @property string $typeId
 * @property int $translationEntryId
 */
class Translation extends yiidbActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'translation';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['sourceEntryId', 'languageId', 'typeId', 'translationEntryId'], 'required'],
            [['sourceEntryId', 'translationEntryId'], 'integer'],
            [['languageId', 'typeId'], 'string', 'max' => 10],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'sourceEntryId' => Yii::t('app', 'Source Entry ID'),
            'languageId' => Yii::t('app', 'Language ID'),
            'typeId' => Yii::t('app', 'Type ID'),
            'translationEntryId' => Yii::t('app', 'Translation Entry ID'),
        ];
    }

    public static function translate($words)
    {
        //<TO DO>:Find and return the translation.
    }

    /**
     * {@inheritdoc}
     * @return TranslationQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new TranslationQuery(get_called_class());
    }
}

config/web.php

<?php

$params = require __DIR__ . '/params.php';
$db = require __DIR__ . '/db.php';

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'aliases' => [
        '@bower' => '@vendor/bower-asset',
        '@npm'   => '@vendor/npm-asset',
    ],
    'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'xxxxxxxx',
            'parsers' => [
                'application/json' => 'yiiwebJsonParser',
            ]
        ],
        /*'response' => [
            'format' => yiiwebResponse::FORMAT_JSON,
            'charset' => 'UTF-8',
            // ...
        ],*/
        'cache' => [
            'class' => 'yiicachingFileCache',
        ],
        'user' => [
            'identityClass' => 'appmodelsUser',
            'enableAutoLogin' => true,
        ],
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
        'mailer' => [
            'class' => yiisymfonymailerMailer::class,
            'viewPath' => '@app/mail',
            // send all mails to a file by default.
            'useFileTransport' => true,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yiilogFileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'db' => $db,
        
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => true,
            'enableStrictParsing' => false
        ]
    ],
    'params' => $params,
];

if (YII_ENV_DEV) {
    // configuration adjustments for 'dev' environment
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = [
        'class' => 'yiidebugModule',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];

    $config['bootstrap'][] = 'gii';
    $config['modules']['gii'] = [
        'class' => 'yiigiiModule',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];
}

return $config;

My environment:
PHP 8.1.2-1ubuntu2.14 (cli) (built: Aug 18 2023 11:41:11) (NTS)
Zend Engine v4.1.2
with Zend OPcache v8.1.2-1ubuntu2.14
with Xdebug v3.2.1
Yii2 (2.0.48.1)

Thank you very much for your help.

I googled for a solution, but I didn’t find a anything.

2

Answers


  1. Chosen as BEST ANSWER

    Today I was reading the Yii documentation, specifically I was reading the handle request section and I came across how Yii process POST requests.

    Here is an extract of the documentation:

    When implementing RESTful APIs, you often need to retrieve parameters that are submitted via PUT, PATCH or other request methods. You can get these parameters by calling the yiiwebRequest::getBodyParam() methods. For example,

    $request = Yii::$app->request;

    // returns all parameters
    $params = $request->bodyParams;

    // returns the parameter "id"
    $param = $request->getBodyParam('id');

    Info: Unlike GET parameters, parameters submitted via POST, PUT, PATCH etc. are sent in the request body. The request component will parse these parameters when you access them through the methods described above. You can customize the way how these parameters are parsed by configuring the yiiwebRequest::$parsers property.
    --End of extract

    We also need to add this in the config/web.php file:

    ...
    'components' => [
    'request' => [
                'cookieValidationKey' => 'Your key',
                'parsers' => [
                    'application/json' => 'yiiwebJsonParser',
                ]
            ],
    ...
    

  2. Answering this..

    "Bad Request (#400): Missing required parameters: words"

    Yes, you’re sending words, as I see in your js, so it might be your Yii2 how it handle the request.

    As I check your controller RestTranslationController, the actionTranslate($words) expecting your words parameter, but in your POST request, parameters are usually sent in the body, not in url.

    This is how I come up that this is causing you the error.
    Can you do modified it to this?

    public function actionTranslate()
    {
       $words = Yii::$app->request->post('words');
       return Translation::translate($words);
    }
    

    We use Yii::$app->request->post('words') here to ensure that your controller will get the parameter(words) and hoping it will not causing you Bad Request error.

    Edit

    public function actionTranslate()
    {
      $request_raw = file_get_contents('php://input');
      $request = json_decode($request_raw, true);
      $words = $request['words'];
      return Translation::translate($words);
    }
    

    Used php://input to read raw data from the request body.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search