kamatamaudon
@kamatamaudon

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

PHP portersAPIと連携して登録する方法

解決したいこと

PHPで求人webサイトを作成しています。
portersAPIを使用してユーザー登録を行いたいですが、登録途中にエラーが出ます。
原因と解決策を教えていただければと思います

発生している問題・エラー

登録に失敗したので、原因を調べるためにvar_dumpしたところ、403が返ってきました。

// $_SESSION[$session_name]['error'] = "登録に失敗しました。";となります

var_dump($contents);exit(); // string(88) "403"
var_dump($obj);exit(); // array(1) { ["Code"]=> string(3) "403" }
var_dump($result);exit();//false

該当するソースコード

	//************************************************
	// PORTERS認証用クラス(トークン取得など)
    //************************************************
	Class portersAuth {
    		//テスト用
		const APP_ID = "アプリケーションID";
		const SECRET = "アプリケーションのシークレットキー";
		const PARTITION_NO = "xxxx"; //テスト用
        
		function getToken(){
			// ** OAuth START ** 
			$oAuthUrl = "https://api-hrbc-jp.porterscloud.com/v1/oauth?app_id=".self::APP_ID."&response_type=code_direct";

			// PORTERSのOAuthにアクセスしてcode取得
			$xmlObj = simplexml_load_file($oAuthUrl);
			$oAuthResponce = json_decode(json_encode($xmlObj), true);
		
			// エラーチェック
			$this->checkErr($oAuthResponce, "OAuth");
			
			// 戻り値のcode
			$code = $oAuthResponce['Code'];
			// ** OAuth END ** 
		
			// ** Token START **
			$tokenUrl = "https://api-hrbc-jp.porterscloud.com/v1/token";
			// Tokne取得API送信パラメータ
			$data = array(
				"app_id" => self::APP_ID, //アプリケーションID。
				"secret" => self::SECRET, //アプリケーションのシークレット。
				"grant_type" => "oauth_code", //認証フローのタイプ(oauth_code)。
				"code" => $code //上記OAuthプロセスで取得したコード。
			);
			$contentData = http_build_query($data, "", "&");
			// ヘッダー設定
			$options = array(
				'http'=>array(
					'method'=>'POST',
					'header'=>"Content-Type:application/x-www-form-urlencoded",
					'content'=>$contentData
				)
			);
			$context = stream_context_create( $options );
			// 送信実行
			$contents = file_get_contents(
				$tokenUrl
				, FALSE
				, $context
			);
			$tokenXml = new SimpleXMLElement($contents); //simplexml_load_fileでXMLデータを読み込む。
			$tokenResponce = json_decode(json_encode($tokenXml), true); //json_decode(json_encode())でXMLを配列に変換。

		
			// エラーチェック
			$this->checkErr($tokenResponce, "Token");
			// トークン取得
			$accessToken = $tokenResponce['AccessToken'];
	
			return $accessToken;
			// ** Token END **
		}

		function getPartitionNo($accessToken){
			// ※※パーティションは基本的に変更しないのでコメントアウト。$pertitinNoに設定済み※※
			$partitionUrl = "https://api-hrbc-jp.porterscloud.com/v1/partition?request_type=1&count=1&start=0";
			// ヘッダー設定(token利用)
			$options = array(
				'http'=>array(
					'method'=>'GET',
					'header'=>"Content-Type:application/xml; charset=UTF-8\r\n"  // Content-Type: XMLデータとして指定。
						."X-porters-hrbc-oauth-token:".$accessToken   // X-porters-hrbc-oauth-token: 認証トークン。
				)
			);
			$context = stream_context_create( $options );

			// 送信実行 (file_get_contentsでHTTP GETリクエストを送信。)
			$contents = file_get_contents(
				$partitionUrl
				, FALSE
				, $context
			);

			$partitionXml = new SimpleXMLElement($contents); // SimpleXMLElementでXMLデータを処理。
			$partitionResponce = json_decode(json_encode($partitionXml), true);

			// エラーチェック
			$this->checkErr($partitionResponce, "Partition");
			$pertitionNo = $partitionResponce['Item']['Partition.P_Id']; //結果からパーティション番号を抽出

			return $pertitionNo;
		}
		
		//. エラーチェック (checkErr) このメソッドは、各APIのレスポンスに対してエラーをチェックします。
		function checkErr($arr, $apiName){
			if(!is_array($arr)){
				echo("API:".$apiName."<br>");
				echo("エラーが発生しました。<br>アクセス先・パラメータを確認してください<br>");
				exit;
			}
			if($apiName != "OAuth" && $apiName != "Partition" && array_key_exists('Code', $arr)){
				echo("API:".$apiName."<br>");
				echo("エラーが発生しました。<br>エラーコード表で確認してください<br>https://hrbcapi.porters.jp/hc/ja/articles/115008171708-Result-Code-List<br>");
				echo("System Code:".$arr['Code']."<br>");
				exit;
			}
			if(array_key_exists('Error', $arr) &&  $arr['Error'] != 0){
				echo("API:".$apiName."<br>");
				echo("エラーが発生しました。<br>");
				echo("Error:".$arr['Error']."<br>");
				echo("Message:".$arr['Message']."<br>");
				exit;
			}
			return;
		}		
	}




// ********************************************************
// porters登録関数
// ********************************************************

// 求職者の情報を XML 形式で作成し、Porters API に送信して登録を行う PHP の関数です。
function writeCandidate($data,$set_jobno,$kinmu_area) {
	// ---------------------------------------------------------
	// 認証
	// ---------------------------------------------------------
	// PORTERSアクセス用クラス
	$clsPortersAuth	 = new portersAuth();
	// 認証用トークン取得
	$accessToken	 = $clsPortersAuth->getToken();

	$_sex_flg		 = common::_sex_flg();
	$_kibou_flg		 = common::_kibou_flg();
	$_infotype_flg = common::_infotype_flg();
	$P_Owner		 = 32;
	$P_Id			 = '-1';
// ---------------------------------------------------------
// 1.Candidate - Write    Candidate...エンティティを XML で作成。
// ---------------------------------------------------------
// 求職者登録を行い、求職者のIDを採番
	$xml_data		 = '';
	$xml_data		 .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
	$xml_data		 .= '<Candidate>';
	$xml_data		 .= '<Item>';
	$xml_data		 .= '<Person.P_Id>' . $P_Id . '</Person.P_Id>'; 
	$xml_data		 .= '<Person.P_Owner>' . $P_Owner . '</Person.P_Owner>';
	$xml_data		 .= '<Person.P_Name>' . mChgString($data['name1']) . ' ' . mChgString($data['name2']) . '</Person.P_Name>';   //名前(姓と名)を設定。


	$wk   =   mChgString($data['name_kana1'] . ' ' . $data['name_kana2']);
	$wk2  =   mb_convert_kana($wk, "k");

	$xml_data		 .= '<Person.P_Reading>' . $wk2. '</Person.P_Reading>';   //カタカナの読みを設定。

	$zip1 =  substr($data["zipcode"], 0, 3);
	$zip2 =  substr($data["zipcode"], 3);
	$zip = sprintf("%03d-%04d",$zip1,$zip2);

	$xml_data		 .= '<Person.P_Zipcode>'.$zip.'</Person.P_Zipcode>';   //(郵便番号)などの基本情報を設定。
	$xml_data		 .= '<Person.P_Prefecture>' . $data['pref'] . '</Person.P_Prefecture>';   //
	$xml_data		 .= '<Person.P_City>'      . mChgString($data['adress']) . '</Person.P_City>';   //
	$xml_data		 .= '<Person.P_Street>'    . mChgString($data['street'])  . '</Person.P_Street>';   //
	$xml_data		 .= '<Person.P_Telephone>' . mChgString($data['tel']) . '</Person.P_Telephone>';   //
	$xml_data		 .= '<Person.P_Mail>' . $data['email'] . '</Person.P_Mail>';   //


	//メモ
	$memo =""; //希望する求人番号 (set_jobno) や登録希望の種類 (instype) などのメモ

	// 希望する求人番号 (set_jobno)
	if( $set_jobno !="" ){
		$memo = "[JOB NO]".$set_jobno."\n";
	}

	// 登録希望の種類
	$instype = "";
	$_instype = common::_form_instype();
	if( isset($data["instype"]) ){
		if( is_array($data["instype"]) ){
			if( count($data["instype"])>0 ){
				foreach($data["instype"] as $k1=>$v1){
					foreach($_instype as $k2=>$v2){
						if( $v1==$k2 ){
							if( $instype != "" ){
								$instype .= ",";
							}
							$instype .= $v2;
						}
					}
				}
			}
		}
	}
	if( $instype !="" ){
		$memo .= "[登録希望の種類]".$instype."\n";
	}


	//★希望沿線
	$_ensen = common::_formensen();	//common::_formensen() で沿線の一覧を取得。

	$ensen = "";
	if( isset($data["ensen"]) ){
		if( is_array($data["ensen"]) ){
			if( count($data["ensen"])>0 ){

				foreach($data["ensen"] as $k1=>$v1){
					foreach($_ensen as $k2=>$v2){
						if( $v1==$v2['p_alias'] ){
							if( $ensen != "" ){
								$ensen .= ",";
							}
							$ensen .= $v2["p_name"];
						}
					}
				}

			}
		}
	}

	if( $ensen !="" ){
		$memo .= "[希望の沿線]".$ensen."\n";
	}

	$xml_data		 .= '<Person.P_Memo>'.$memo.'</Person.P_Memo>';   //
	$xml_data		 .= '</Item>';
	$xml_data		 .= '</Candidate>';

// $xml_dataおわり

	$url			 = "https://api-hrbc-jp.porterscloud.com/v1/candidate?partition=" . $clsPortersAuth::PARTITION_NO;

	$options = array('http' => array(
			'method'	 => 'POST',
			'header'	 => 'Content-Type:application/xml; charset=UTF-8' . "\r\n"
			. 'Content-length: ' . strlen($xml_data) . "\r\n"
			. 'X-porters-hrbc-oauth-token:' . $accessToken,
			'content' => $xml_data
	));

	$context		 = stream_context_create($options); //resource(26) of type (stream-context)
	$contents		 = file_get_contents( // file_get_contents を使って POST リクエストを送信。
		$url
		, FALSE
		, $context
	);
	// APIリクエストの前にデータを確認
log_out('Request XML: ' . $xml_data);
log_out('Access Token: ' . $accessToken);

// レスポンスヘッダーも確認
$http_response_header_str = print_r($http_response_header, true);
log_out('Response Headers: ' . $http_response_header_str);

if ($contents === FALSE) {
    $error = error_get_last();
    log_out('Error during API call: ' . print_r($error, true));
    return false;
}

// レスポンスの詳細なチェック
$response_code = 0;
foreach ($http_response_header as $header) {
    if (strpos($header, 'HTTP/') === 0) {
        $parts = explode(' ', $header);
        $response_code = intval($parts[1]);
        break;
    }
}
log_out('Response Code: ' . $response_code);
	
	// APIレスポンス内容を確認
	$response = json_decode($contents, true);

	log_out('API Response: ' . print_r($response, true));
	
	if (isset($response['Error']) && $response['Error'] != 0) {
		log_out('API Error: ' . $response['Message']);
		return false;
	}
		$pos			 = strpos($http_response_header[0], '200'); //API からのレスポンスが 200 OK でない場合、エラーログを記録し、エラー処理 (mErrSend) を行う。
	if ($pos === false) { //エラーの場合
		// 例外処理
		$err_msg = '';
		$err_msg = '[FileName]:' . __FILE__ . '[Line]:' . __LINE__ . '[Data]:' . (var_export($data, true) . PHP_EOL);
		log_out($err_msg);
		mErrSend($err_msg);
	} else { //エラーじゃない場合、処理する
		$obj = json_decode(json_encode(simplexml_load_string($contents)), true);
		// var_dump($obj);exit(); //array(1) { ["Code"]=> string(3) "403" }

		if (empty($obj['Code']) && $obj['Item']['Code'] == 0) {

			$data['Resume.P_Owner']		 = $P_Owner;
			$data['Resume.P_Candidate']	 = $obj['Item']['Id'];
			$data['Resume.P_Gender']	 = $data['sex']; //性別
			$data['Resume.P_ExpectArea'] = $data['area']; //希望の場所

			$data['Resume.U_A186DE5ABE9094B821987B1A0FAFCC'] = $data['name_kana1'].' '.$data['name_kana2'];	//カナ

			if( $data['birth'] =="" ){
				$data['Resume.P_DateOfBirth'] = "";		//生年月日
			}else{
				$data['Resume.P_DateOfBirth'] = str_replace('-', '/', $data['birth']);		//生年月日
			}
			$data['Resume.U_8C7724F2E21F1CE2D2EB5F9A429528']    = $data['insmode'];		//登録モード

			//郵便番号 ?
			$data['Resume.U_491057B396F015B8F3DC4FCA44A24D']    = $data['zipcode'];

			$data['Resume.U_5CE1876496D75761501E22CAA2DB06']	= $data['pref'];	//都道府県

			$data['Resume.P_CityReference']						= $data['adress'];	//市区町村					
			$data['Resume.P_StreetReference']					= $data['street'];	//番地


			//最寄り駅
			$station ="";



			foreach($_ensen as $k=>$v){

				if( $v["p_alias"]==$data['m_ensen'] ){
					$station .= "".$v["p_name"];
				}
				
			}

			$station .= " ".$data['station'];

			$data['Resume.U_7DA891042ECA7D493A3425751F4AEA']	= $station;	//沿線 最寄駅名

			$data['Resume.U_7D2DD08E787F33AF37409119CA8CF7']	= $data['ekikara'];	//駅から(手段) 名称変換
			$data['Resume.U_E071AAF5F69DE35209BA5B859C4DDA']	= $data['syoyoutime'];	//	所要時間(分) 数字
			// $data['Resume.P_MobileReference']	 				= $data['tel1'].$data['tel2'].$data['tel3'];	//	電話番号
			$data['Resume.P_MobileReference']	 				= $data['tel'];	//	電話番号
			$data['Resume.P_Mail']	 							= $data['email'];	//メールアドレス
			$data['Resume.P_RegisterChannel']					= $data['infodata'];	//情報源
			$data['Resume.U_4306BC9BD2DA298D90D3A4626B8377']	= "";
			


			//就業希望情報
			$data['Resume.U_8AB5D82AABBC48A63BFF927F78532B']	= $data['kind'];				//紹介希望					

			//希望業務名
			$workdata = array();
			if( $data['jobcategory1'] !="" ){
				$workdata[] = $data['jobcategory1'];
			}
			if( $data['jobcategory2'] !="" ){
				$workdata[] = $data['jobcategory2'];
			}
			if( $data['jobcategory3'] !="" ){
				$workdata[] = $data['jobcategory3'];
			}

			$data['Resume.P_ExpectJobCategory']					= $workdata ;			        //希望業務名				
			$data['Resume.P_ExpectCondition']					= $data['expectcondition'];		//希望給与					
			$data['Resume.P_DesiredHourlyRate']					= $data['desiredhourlyrate'];	//希望の時給[円] 文字		
			$data['Resume.U_58CABC7C23AC9DA484F90A2C484BF4']	= $data['free'];				//希望休日					
			$data['Resume.P_ExpectArea']						= $data['area'];				//希望勤務地その他
			$data['Resume.U_72F2F0BB897EE20BE97F558B5FECF7']	= $data['worktype'];			//希望期間					
			$data['Resume.U_249C06A7B2798935EC31590B5D8118']	= $data['worktime'];			//希望就業時間				
			$data['Resume.U_62BBA993B6C50E3C9151514B7CF6C0']	= $data['workover'];			//残業						
			$data['Resume.U_0B8655796CC968A0FE5072FAC34F90']	= $data['kibou'];				//稼働可能日 就業開始可能日
			//	学歴
			$data['Resume.U_220238CFB049EBC7AE905C65F87A28']	= $data['gakureki'];			//学歴 //プルダウン選択 高校卒業/短期大卒業/大学卒業/大学院卒業/専門学校卒業/短大卒業を変換
			$data['Resume.U_E182D9B71AE22AD4FF1D0A01DE456F']	= $data['comp'];			    //卒業
			$data['Resume.P_Education']							= $data['gakubu'];				//学部名 学歴(詳細)に

			$data['Resume.P_ChangeJobsCount']					= $data['company_su'];			//就業企業数

			$_kind    = common::_formkind();				//雇用形態
			$_jobcategory =common::_form_job_category_new();

			$rireki=array();
			$experiencedJobCategory=array();

			for($i=0;$i<=3;$i++){

				$rireki[$i] .="職歴期間(開始):".$data['shoku'.($i+1).'_start']."\n";
				$rireki[$i] .="職歴期間(終了):".$data['shoku'.($i+1).'_end']."\n";
				$rireki[$i] .="職歴会社名:".$data['shoku'.($i+1).'_compnay']."\n";
				
				$chk=$data['shoku'.($i+1).'_syokusyu'];
				$set="";
				
				foreach($_jobcategory as $k=>$v){
					if( $chk == $v["p_alias"] ){
						$set= $v["p_name"];
						$experiencedJobCategory[] = $chk;
					}
				}
				$rireki[$i] .="職歴 職種:".$set."\n";
				$rireki[$i] .="職歴 職務内容:".$data['shoku'.($i+1).'_naiyou']."\n";

				$chk=$data['shoku'.($i+1).'_keitai'];
				$set="";
				foreach($_kind as $k=>$v){
					if( $chk == $v["p_alias"] ){
						$set= $v["p_name"];
					}
				}
				$rireki[$i] .="職歴 雇用形態:".$set."\n";
			}
			$data['Resume.U_BD7BE24CBBA17CC929D0E4F3535F97']	= $rireki[0];
			$data['Resume.U_B1537A89B5B97D06AA244561D3E349']	= $rireki[1];
			$data['Resume.U_7CBFF2B56D7C0C44866E498E80EA65']	= $rireki[2];

			// 同じキーがあるとエラーになるので重複を削除
			$unique = array_unique($experiencedJobCategory);
			$data['Resume.P_ExperiencedJobCategory']	= $unique;

			//■
			$data['Resume.U_F469D5F0F5C96AD0EB1750D62BE3FF']	= $data['license1'];
			$data['Resume.U_B39027F36266688A68654C25E722DF']	= $data['license2'];
			$data['Resume.U_2D81251C2B2965EA9DB3C04C710B68']	= $data['license3'];
			$data['Resume.U_CFA750A4CCC6532E7F06E962DB068A']	= $data['license4'];
			$data['Resume.U_F7C31BEC2497B8B8F0D84EA7718921']	= $data['license_other'];

			//スキル
			$data['Resume.U_4170473D23B0D8C9F5BFB8CD22493F']	= $data['skill'];			//スキル

			return _resume_write($data); //成功 // 履歴書データ(Resume)を作成または更新するための処理


		} else {
			// 例外処理
			$err_msg = '';
			$err_msg = '[FileName]:' . __FILE__ . '[Line]:' . __LINE__ . '[Data]:' . (var_export($data, true) . PHP_EOL);
			log_out($err_msg);
			mErrSend($err_msg);
		}
	}
}


$data = [
  'insmode' => 'Option.U_041394',
  'instype' => 
  array (0 => '1',),
  'name1' => '姓',
  'name2' => '名',
  'name_kana1' => 'セイ',
  'name_kana2' => 'メイ',
  'email' => 'test@gmail.com',
  'tel' => '09000000000',
  'birth' => '1992-01-15',
  'zip_code' => '8160845',
  'pref' => '東京都',
  'adress' => '新宿区',
  'street' => '1-2-3',
  'password' => 'passworddayo',
  'ensen' => 'Option.U_040517',
  'station' => '天神',
  'ekikara' => 'Option.U_039887',
  'syoyoutime' => '10',
  'infodata' => 'Option.U_000101',
  'largecate1' => '1',
  'jobcategory1' => 'Option.U_041229',
  'largecate2' => '1',
  'jobcategory2' => 'Option.U_041228',
  'largecate3' => '4',
  'jobcategory3' => 'Option.U_041265',
  'expectcondition' => '1500',
  'desiredhourlyrate' => '230000',
  'free' => 'Option.U_040513',
  'worktype' => 'Option.U_040190',
  'worktime' => 'Option.U_001188',
  'workover' => 'Option.U_001010',
  'kibou' => '2025-04-01',
  'gakureki' => 'Option.U_000087',
  'comp' => 'Option.U_000095',
  'gakubu' => '法学部',
  'shoku1_start' => '2020-04-01',
  'shoku1_end' => '2022-03-31',
  'shoku1_company' => '株式会社テスト',
  'shoku1_syokusyu_dai' => '1',
  'shoku1_syokusyu' => 'Option.U_041228',
  'shoku1_naiyo' => 'データ入力 電話応対',
  'shoku1_keitai' => 'Option.P_FullTime',
  'shoku2_start' => '2022-04-04',
  'shoku2_end' => '2023-12-28',
  'shoku2_company' => '株式会社テスト2',
  'shoku2_syokusyu_dai' => '2',
  'shoku2_syokusyu' => 'Option.U_041248',
  'shoku2_naiyo' => '営業 見積書作成',
  'shoku2_keitai' => 'Option.P_Contractor',
  'shoku3_start' => '2024-01-08',
  'shoku3_end' => '2025-02-19',
  'shoku3_company' => '株式会社テスト3',
  'shoku3_syokusyu_dai' => '4',
  'shoku3_syokusyu' => 'Option.U_041265',
  'shoku3_naiyo' => '開発 テスト',
  'shoku3_keitai' => 'Option.P_TemporaryStaff',
  'license1' => '001',
  'license2' => '002',
  'license3' => '052',
  'license4' => '112',
  'license_other' => 'ここに資格が入ります テスと',
  'zipcode' => '8160845',
  'kind' => 
  array (0 => 'Option.P_TemporaryStaff',1 => 'Option.P_FullTime',2 =>'Option.P_Contractor',),
  'area' => array (0 => 'Option.U_039306',1 => 'Option.U_039307',),
  'area_ta' => array (0 => 'Option.U_040521',1 => 'Option.U_040522',
  ),
  'dataList' => 
  array (
    'image1' => '7a79c35f7ce0704dec63be82440c8182.pdf',
    'hdn_image1' => '250221065703_67b84def72871.pdf',
    'image2' => 'xxxxxxxxxxxxx.pdf',
    'hdn_image2' => '250221065703_67b84def72a61.pdf',
    'image3' => '',
    'hdn_image3' => '',
    'image4' => '',
    'hdn_image4' => '',
    'image5' => '',
    'hdn_image5' => '',
  ),
  'company_su' => '3',
  'skill' => 
  array (
    0 => 'Option.U_003054',
    1 => 'Option.U_003116',
  )
  ];


$result = writeCandidate($data,$set_jobno,'');

テスト環境で行っています。
portersのトークンの取得はできていました。

0

1Answer

原因を調べるためにvar_dumpしたところ

残念ながらこの文脈からでは、コードのどの部分であるのか判断できませんでした。
コードの中に$_SESSIONは登場しませんし、$contentsなども複数登場するからです。

403が返ってきました

403は、アクセス権がないために要求が拒否されたことを示しています。
ですので、認証関係に問題があるのではないかと思われます。

下記の点を確認されてはいかがでしょうか。

  • アプリケーションIDやシークレットキーは提供されたものと異なっていないか
  • 使用している環境は正しいものか
  • 認証、アクセストークンの取得は正しく行えているか
    • URL
    • リクエストパラメータ
    • レスポンス取得
  • 有効なアクセストークンを使用しているか(有効期限は過ぎていないか)

参考

0Like

Your answer might help someone💌